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

G++ и clang++ различное поведение с интегральным параметром шаблона

У меня есть следующий код С++ 11.

#include <type_traits>

using IntType = unsigned long long;

template <IntType N> struct Int {};

template <class T>
struct is_int : std::false_type {};

template <long long N>
struct is_int<Int<N>> : std::true_type {};

int main()
{
    static_assert (is_int<Int<0>>::value, "");
    return 0;
}

Clang++ 3.3 компилирует код, но в g++ 4.8.2 статическое утверждение терпит неудачу

$ g++ -std=c++11 main.cpp 
main.cpp: In function ‘int main()’:
main.cpp:15:5: error: static assertion failed: 
     static_assert (is_int<Int<0>>::value, "");
     ^
$ 

Проблема вызвана различными интегральными параметрами шаблона. Какой компилятор прав в этом случае?

4b9b3361

Ответ 1

Удивление

Это тонкая ошибка Кланга, глубоко похороненная в Стандарте. Проблема состоит в том, что почти во всех случаях аргументы шаблона без типа могут быть преобразованы в тип параметра шаблона. Например. выражение Int<0> имеет логический аргумент int значения 0, который преобразуется в тип unsigned long long параметра шаблона N.

14.8.2 Вывод аргумента шаблона [temp.deduct]/2 2nd bullet

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

Поскольку ваш шаблон шаблона is_int<T> имеет частичную специализацию, нам нужно посмотреть

14.5.5.1 Соответствие частичных специализаций шаблона шаблона [temp.class.spec.match]

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

2 Частичная специализация соответствует заданному фактическому аргументу шаблона если аргументы шаблона частичной специализации могут быть выведенный из фактического списка аргументов шаблона (14.8.2).

Итак, мы можем перейти к более ранней цитате 14.8.2/2 2nd bullet и соответствовать второй специализации (хотя в этом случае нужно будет играть еще более сложную игру с разрешением перегрузки).

Разрешение

Однако, оказывается (как упоминалось в комментариях @DyP в комментариях), что другое предложение в Стандарте заменяет это:

14.8.2.5 Вывод шаблонных аргументов из типа [temp.deduct.type]

17 Если в объявлении шаблона функции с не-типом шаблон-параметр, шаблонный шаблонный шаблон не используется выражение в списке параметров функции и, если соответствующий выводится шаблон-аргумент, тип шаблона-аргумента должен совпадать тип параметра шаблона точно, за исключением того, что a шаблон-аргумент, выведенный из привязки массива, может быть любого интеграла тип. [Пример:

template<int i> class A { / ... / };
template<short s> void f(A<s>);
  void k1() {
  A<1> a;
  f(a); // error: deduction fails for conversion from int to short
  f<1>(a); // OK
}

Результат состоит в том, что неполная специализация is_int не может быть выведена, поскольку она не принимает точный тип (unsigned long long vs long long) в качестве формального шаблона шаблона шаблона int.

Вы можете решить эту проблему, указав параметр N непигового шаблона <частичного специализации is_int того же типа, что и непиковый параметр N в основном шаблоне int.

template <IntType N>
//        ^^^^^^^^         
struct is_int<Int<N>> : std::true_type {};

Пример Live.

Ответ 2

Clang является непоследовательным. Поскольку он принимает ваш код, я ожидаю, что следующий код должен вывести f(Int<long long>) вместо f(T):

using IntType = unsigned long long;
template <IntType N> struct Int {};

template<typename T>
void f(T) { std::cout << "f(T)" << std::endl; }

template<long long N>
void f(Int<N>) { std::cout << "f(Int<long long>)" << std::endl; }

int main()
{
    f(Int<0>{});
}

Но удивительно, что он выводит эту (онлайн-демонстрацию):

f(T)

Это показывает, что Int<0> НЕ соответствует второй перегрузке, которая принимает аргумент как Int<N>. Если это так, то почему он соответствует с Int<N>, когда он используется в качестве аргумента шаблона для шаблона класса (в вашем случае)?

Мой вывод:

  • Если Clang корректен в случае my, то он неверен в вашем случае.
  • Если Clang корректен в вашем случае, то он неверен в случае my.

В любом случае у Клана есть ошибка.

GCC, с другой стороны, по крайней мере согласован. Это не доказывает, что у него нет ошибки; это может означать, что в обоих случаях есть ошибка! Если кто-то не придумает стандартную версию и не покажет, что у нее тоже есть ошибка, я буду доверять GCC в этом случае.