Ограничивая int to bool в SFINAE, различный вывод между gcc и clang

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

template<int i>
struct nice_type;

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

template<int i>
struct is_nice< nice_type<i> > : std::integral_constant<int, i> {};

template<class T, class = void>
struct pick
{
    typedef std::integral_constant<int, -1> type;
};

template<class T>
struct pick<T, typename std::enable_if< is_nice<T>::value >::type >
{
    typedef std::integral_constant<int, is_nice<T>::value > type;
};

int main()
{
    std::cout << pick<int>::type::value << ", ";
    std::cout << pick< nice_type<42> >::type::value << std::endl;
    return 0;
}

Clang (3.4.1) выводит "-1, -1", а GCC (4.9.0) выводит "-1, 42".

Задача лежит в специализации pick. Хотя Gcc кажется счастливым преобразовать is_nice<T>::value (42) в bool(true), clang не делает этого и отбрасывает специализацию. Оба примера скомпилированы с помощью -std=c++11.

Какой компилятор прав?

Ответ 1

Это ошибка gcc 57891. Преобразование интегральной константы 42 в bool включает сужение преобразования, которое не допускается в аргументах шаблона, отличных от типа. Следовательно, enable_if плохо сформирован, а специализация pick должна быть отброшена, как правильно делает clang.

§14.3.2/5 [temp.arg.nontype]

Следующие преобразования выполняются для каждого выражения, используемого в качестве non-type template-argument. Если аргумент шаблона не-типа не может быть преобразуется в тип соответствующего шаблона-параметра, тогда программа плохо сформирована.
- для шаблона-шаблона, не относящегося к типу интегральный или перечисляемый тип, конверсии, разрешенные в преобразованном постоянное выражение (5.19).
...

§5.19/3 [expr.const]

... A преобразованное константное выражение типа T является выражением, неявно преобразованным в prvalue типа T, где преобразованное выражение является ядром константное выражение и неявная последовательность преобразований содержат только определенные пользователем преобразования, преобразования lvalue-to-rvalue (4.1), интегральные продвижения (4.5) и интегральные преобразования (4.7) , кроме сужения конверсий (8.5.4).

§8.5.4/7 [dcl.init.list]

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


Этот минимальный пример демонстрирует ошибку gcc:

template<bool>
struct foo{};
foo<10> f;

int main() {}

gcc-4.9 принимает код, в то время как clang-3.4 отклоняет его со следующей ошибкой:

ошибка: аргумент шаблона не-типа оценивается до 10, который не может быть сужен, чтобы набрать 'bool' [-WС++ 11-сужение]

 foo<10> f;
     ^

Исправить вашу конкретную проблему легко. Убедитесь, что аргумент шаблона непигового типа enable_if оценивается как bool

template<class T>
struct pick<T, typename std::enable_if< is_nice<T>::value != 0 >::type >
//                                                       ^^^^^^
{
    typedef std::integral_constant<int, is_nice<T>::value > type;
};