Подтвердить что ты не робот

Разница в поведении разрешения перегрузки между GCC и clang (SFINAE)

GCC принимает следующий код:

template <typename T>
struct meta
{
    typedef typename T::type type;
};

struct S {};

template <typename T>
typename meta<T>::type foo(T, S);

int foo(int, int);      

int main()
{
    foo(0, 0);
}

Но clang отвергает его со следующей ошибкой:

test.cpp:4:22: error: type 'int' cannot be used prior to '::' because it has no members
    typedef typename T::type type;
                     ^
test.cpp:10:10: note: in instantiation of template class 'meta<int>' requested here
typename meta<T>::type foo(T, S);
         ^
test.cpp:10:24: note: while substituting deduced template arguments into function template 'foo' [with T = int]
typename meta<T>::type foo(T, S);
                       ^

Это, по-видимому, указывает на разницу в порядке, в котором GCC и clang выполняют определенные операции при разрешении перегрузки. GCC, похоже, выбрасывает кандидата шаблона из-за несоответствия типа во втором параметре (S vs. int), прежде чем пытаться создать экземпляр возвращаемого типа кандидата шаблона, в то время как clang, похоже, делает это наоборот.

Кто прав?

Я считаю, что этот вопрос имеет важные последствия для авторов шаблонных библиотек. В частности, если правообладатель прав, автор шаблона foo должен будет выполнить дополнительную работу, чтобы превратить ошибку в сбой замены.

EDIT. Обратите внимание, что следующий несколько более простой пример отклоняется как GCC, так и clang с аналогичными ошибками:

template <typename T>
struct meta
{
    typedef typename T::type type;
};

template <typename T>
typename meta<T>::type foo(T);

int foo(int);      

int main()
{
    foo(0);
}

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

ОБНОВЛЕНИЕ. Ответ Luc Danton убедил меня, что clang является правильным, чтобы отклонить код. Поэтому я подал ошибку GCC.

4b9b3361

Ответ 1

Оба Clang и g++ верны здесь.

Ответ Per Luc Danton, компилятору разрешено выводить T = int для шаблона функции foo. Затем при замене этого значения в объявлении foo требуется неявное создание экземпляра meta<int>, и оно приводит к ошибке за пределами непосредственного контекста подстановки (поэтому SFINAE не применяется). Итак, Clang правильно отклоняет этот код.

Однако, [temp.inst] p7 говорит:

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

Поскольку не-шаблон foo является точным соответствием для аргументов в вызове, компилятор может определить, что специализация шаблона функции никогда не будет лучшей жизнеспособной функцией, и поэтому не нужно выполнять дедукцию и подстановку аргументов. Поэтому g++ корректно не отклонять этот код.

Ответ 2

С++ 03 использует эту формулировку в качестве части спецификации для того, что обычно называется SFINAE (14.8.2. Дезактивация аргумента шаблона [temp.deduct], параграф 2):

[...] Если подстановка в параметре шаблона или в тип функции шаблона функции приводит к недопустимому типу, вывод типа невозможен. [...]

В отличие от этого, С++ 11 использует эту формулировку (14.8.2 Вывод аргумента шаблона [temp.deduct], пункт 8):

Если подстановка приводит к недопустимому типу или выражению, тип дедукции не выполняется. [...] Только недопустимые типы и выражения в непосредственном контексте типа функции и его типы параметров шаблона могут привести к отказу дедукции. [...]

Акцент мой. Насколько я понимаю, формулировка была улучшена на С++ 11, чтобы однозначно наметить, что должно привести к SFINAE (так называемые мягкие ошибки) и что не должно (жесткие ошибки). Этот документ 2008 года является примером дискуссии, которая продолжалась в то время и приводила к действующим правилам.

С учетом этого может случиться, что в соответствии с С++ 03 реализация может быть права принять ваш код (и даже, возможно, он должен). Я подозреваю, что реализация С++ 11 должна отклонить ее: ошибка (int::type) находится в контексте meta<int>, а не foo<int>.