GOOGLE ADS

понедельник, 9 мая 2022 г.

Частичный порядок шаблона - почему здесь работает частичный вывод

Рассмотрим следующий простой (в той мере, в какой шаблонные вопросы вообще могут быть) пример:

#include <iostream>
template <typename T>
struct identity;
template <>
struct identity<int> {
using type = int;
};
template<typename T> void bar(T, T ) { std::cout << "a\n"; }
template<typename T> void bar(T, typename identity<T>::type) { std::cout << "b\n"; }
int main ()
{
bar(0, 0);
}

И clang, и gcc печатают там «a». Согласно правилам в [temp.deduct.partial] и [temp.func.order], чтобы определить частичный порядок, нам нужно синтезировать некоторые уникальные типы. Таким образом, у нас есть две попытки вывода:

+---+-------------------------------+-------------------------------------------+
| | Parameters | Arguments |
+---+-------------------------------+-------------------------------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA |
| b | T, T | UniqueB, typename identity<UniqueB>::type |
+---+-------------------------------+-------------------------------------------+

Для вывода на «b», согласно ответу Ричарда Кордена, выражение typename identity<UniqueB>::typeрассматривается как тип и не оценивается. То есть это будет синтезировано, как если бы это было:

+---+-------------------------------+--------------------+
| | Parameters | Arguments |
+---+-------------------------------+--------------------+
| a | T, typename identity<T>::type | UniqueA, UniqueA |
| b | T, T | UniqueB, UniqueB_2 |
+---+-------------------------------+--------------------+

Ясно, что дедукция на "b" не работает. Это два разных типа, поэтому вы не можете вывести Tих обоих.

Однако, мне кажется, что вычет на Aдолжен быть неудачным. Для первого аргумента вы должны сопоставить T == UniqueA. Второй аргумент — невыведенный контекст, так что разве этот вывод не был бы успешным, если бы UniqueAего можно было преобразовать в identity<UniqueA>::type? Последнее является ошибкой замещения, поэтому я не понимаю, как этот вывод тоже может быть успешным.

Как и почему gcc и clang предпочитают перегрузку «a» в этом сценарии?


Решение проблемы

Как обсуждалось в комментариях, я считаю, что есть несколько аспектов алгоритма частичного упорядочения шаблона функции, которые неясны или вообще не указаны в стандарте, и это показано в вашем примере.

Чтобы сделать вещи еще более интересными, MSVC (я тестировал 12 и 14) отклоняет вызов как неоднозначный. Я не думаю, что в стандарте есть что-то, что убедительно доказало бы, какой компилятор прав, но я думаю, что у меня есть ключ к разгадке того, откуда берется разница; есть примечание об этом ниже.

Ваш вопрос (и этот ) побудил меня провести дополнительное исследование того, как все работает. Я решил написать этот ответ не потому, что считаю его авторитетным, а для того, чтобы собрать информацию, которую я нашел, в одном месте (она не поместилась в комментариях). Я надеюсь, что это будет полезно.

Во-первых, предлагаемое решение по вопросу 1391. Мы подробно обсуждали это в комментариях и чате. Я думаю, что, хотя это и дает некоторое разъяснение, оно также вносит некоторые вопросы. Он заменяет [14.8.2.4p4] на (новый текст выделен жирным шрифтом):


Каждый указанный выше тип из шаблона параметра и соответствующий тип из шаблона аргумента используются в качестве типов
Pи A. Если конкретное Pне содержит параметров шаблона, которые участвуют в выводе аргумента шаблона, это Pне используется для определения порядка.


На мой взгляд, это не лучшая идея по нескольким причинам:

  • Если Pнезависим, он вообще не содержит никаких параметров шаблона, поэтому он также не содержит никаких параметров, которые участвуют в выводе аргументов, что делает выделенное жирным шрифтом утверждение применимым к нему. Однако это сделало бы template<class T> f(T, int)и template<class T, class U> f(T, U)неупорядоченным, что не имеет смысла. Возможно, это вопрос интерпретации формулировки, но это может вызвать путаницу.

  • Это противоречит понятию, используемому для определения порядка, что влияет на [14.8.2.4p11]. Это делает template<class T> void f(T)и template<class T> void f(typename A<T>::a)неупорядоченным (вывод получается от первого ко второму, т.к. Tне используется в типе, используемом для частичного упорядочения по новому правилу, поэтому может остаться без значения). В настоящее время все компиляторы, которые я тестировал, сообщают о втором как о более специализированном.

  • Это сделает его #2более специализированным, чем #1в следующем примере:

    #include <iostream>
    template<class T> struct A { using a = T; };
    struct D { };
    template<class T> struct B { B() = default; B(D) { } };
    template<class T> struct C { C() = default; C(D) { } };
    template<class T> void f(T, B<T>) { std::cout << "#1\n"; } // #1
    template<class T> void f(T, C<typename A<T>::a>) { std::cout << "#2\n"; } // #2
    int main()
    {
    f<int>(1, D());
    }

    ( #2второй параметр 's не используется для частичного упорядочения, поэтому вывод выполняется от #1до, #2но не наоборот). В настоящее время призыв неоднозначен и, возможно, должен оставаться таковым.


  • Посмотрев на реализацию Clang алгоритма частичного упорядочения, я думаю, что стандартный текст можно изменить, чтобы он отражал то, что происходит на самом деле.

    Оставьте [p4] без изменений и добавьте следующее между [p8] и [p9]:


    Для пары P:A



  • Если Pне зависит, дедукция считается успешной тогда и только тогда, когда Pи Aодного типа.

  • Подстановка выводимых параметров шаблона в невыведенные контексты, появляющиеся в Pне производится и не влияет на результат процесса вывода.

  • Если значения аргументов шаблона успешно выводятся для всех параметров шаблона, за Pисключением тех, которые появляются только в невыведенных контекстах, то вывод считается успешным (даже если некоторые параметры, используемые в P, остаются без значения в конце процесса вывода для этого конкретного контекста). P/ Aпара).



  • Примечания:

  • О втором пункте списка: [14.8.2.5p1] говорит о поиске значений аргументов шаблона, которые Pпосле подстановки выведенных значений (назовем это выведенными A) сделают совместимыми сA. Это может вызвать путаницу в отношении того, что на самом деле происходит во время частичного упорядочения; замены не происходит.

  • В некоторых случаях MSVC, похоже, не реализует третий пункт. Подробнее см. в следующем разделе.

  • Второй и третий пункты предназначены также для случаев, когда Pесть такие формы, как A<T, typename U::b>, которые не охватываются формулировкой в ​​выпуске 1391.

  • Измените текущий [p10] на:


    Шаблон функции Fявляется по крайней мере таким же специализированным, как и шаблон функции
    G, тогда и только тогда, когда:



  • for each pair of types used to determine the ordering, the type from F is at least as specialized as the type from G, and,

  • when performing deduction using the transformed F as the argument template and G as the parameter template, after deduction is done
    for all pairs of types, all template parameters used in the types from
    G that are used to determine the ordering have values, and those
    values are consistent across all pairs of types.



  • F is more specialized than G if F is at least as specialized
    as G and G is not at least as specialized as F.


    Make the entire current [p11] a note.

    (The note added by the resolution of 1391 to [14.8.2.5p4] needs to be adjusted as well - it's fine for [14.8.2.1], but not for [14.8.2.4].)

    For MSVC, in some cases, it looks like all template parameters in P need to receive values during deduction for that specific P / A pair in order for deduction to succeed from A to P. I think this could be what causes implementation divergence in your example and others, but I've seen at least one case where the above doesn't seem to apply, so I'm not sure what to believe.

    Another example where the statement above does seem to apply: changing template<typename T> void bar(T, T) to template<typename T, typename U> void bar(T, U) in your example swaps results around: the call is ambiguous in Clang and GCC, but resolves to b in MSVC.

    One example where it doesn't:

    #include <iostream>
    template<class T> struct A { using a = T; };
    template<class, class> struct B { };
    template<class T, class U> void f(B<U, T>) { std::cout << "#1\n"; }
    template<class T, class U> void f(B<U, typename A<T>::a>) { std::cout << "#2\n"; }
    int main()
    {
    f<int>(B<int, int>());
    }

    This selects #2 in Clang and GCC, as expected, but MSVC rejects the call as ambiguous; no idea why.

    The partial ordering algorithm as described in the standard speaks of synthesizing a unique type, value, or class template in order to generate the arguments. Clang manages that by... not synthesizing anything. It just uses the original forms of the dependent types (as declared) and matches them both ways. This makes sense, as substituting the synthesized types doesn't add any new information. It can't change the forms of the A types, since there's generally no way to tell what concrete types the substituted forms could resolve to. The synthesized types are unknown, which makes them pretty similar to template parameters.

    When encountering a P that is a non-deduced context, Clang's template argument deduction algorithm simply skips it, by returning "success" for that particular step. This happens not only during partial ordering, but for all types of deductions, and not just at the top level in a function parameter list, but recursively whenever a non-deduced context is encountered in the form of a compound type. For some reason, I found that surprising the first time I saw it. Thinking about it, it does, of course, make sense, and is according to the standard ([...] does not participate in type deduction [...] in [14.8.2.5p4]).

    Это согласуется с комментариями Ричарда Кордена к его ответу, но мне пришлось увидеть код компилятора, чтобы понять все последствия (не по вине его ответа, а скорее по моему собственному - программист думает в коде и все такое).

    В этот ответ я включил дополнительную информацию о реализации Clang.

    Комментариев нет:

    Отправить комментарий

    Laravel Datatable addColumn returns ID of one record only

    Я пытаюсь использовать Yajra Datatable для интеграции DataTable на свой веб-сайт. Я смог отобразить таблицу, но столкнулся с проблемой. В по...