Почему 'void * = 0' и 'void * = nullptr' делает разницу?

Я играл с SFINAE и нашел поведение, которое я не могу объяснить.

Это компилирует штраф:

template<typename Integer,
         std::enable_if_t<std::is_integral<Integer>::value>* = nullptr>
void foo(Integer) {}

template<typename Floating,
         std::enable_if_t<std::is_floating_point<Floating>::value>* = nullptr>
void foo(Floating) {}

Хотя это (nullptr заменено на 0):

template<typename Integer,
         std::enable_if_t<std::is_integral<Integer>::value>* = 0>
void foo(Integer) {}

template<typename Floating,
         std::enable_if_t<std::is_floating_point<Floating>::value>* = 0>
void foo(Floating) {}

дает мне ошибку компиляции:

prog.cpp: In function ‘int main(): prog.cpp:13:10: error: no matching function for call to ‘foo(int)
     foo(3);
          ^ prog.cpp:5:6: note: candidate: template<class Integer, std::enable_if_t<std::is_integral<_Tp>::value>* <anonymous> > void foo(Integer)  void foo(Integer) {}
      ^~~ prog.cpp:5:6: note:   template argument deduction/substitution failed: prog.cpp:4:64: error: could not convert template argument ‘0 to ‘std::enable_if_t<true, void>* {aka void*}
          std::enable_if_t<std::is_integral<Integer>::value>* = 0>
                                                                ^ prog.cpp:9:6: note: candidate: template<class Floating, std::enable_if_t<std::is_floating_point<_Tp>::value>* <anonymous> > void foo(Floating)  void foo(Floating) {}
      ^~~ prog.cpp:9:6: note:   template argument deduction/substitution failed: prog.cpp:8:71: note: invalid template non-type parameter
          std::enable_if_t<std::is_floating_point<Floating>::value>* = 0>
                                                                       ^

enable_if_t расширяется void, когда нет замены неудачи, так что я буду иметь что - то вроде void* = 0 из списка параметров шаблона. Почему это нарушает компиляцию?..

Ответ 1

Аргументы шаблона по умолчанию соответствуют их собственным правилам преобразования, которые являются более строгими. Преобразование 0 в тип указателя в частности не применяется.

См. [Temp.arg.nontype]/5.2 (акцент мой):

для шаблона-типа непигового типа указателя на объект применяются квалификационные преобразования ([conv.qual]) и преобразование массива в указатель ([conv.array]); если аргумент template имеет тип std::nullptr_t, применяется преобразование нулевого указателя ([conv.ptr]).

[Примечание. В частности, не применяется ни преобразование нулевого указателя для нулевого интегрального постоянного выражения ([conv.ptr]), ни преобразование с производной базой ([conv.ptr]). Хотя 0 является допустимым аргументом-шаблоном для шаблона-типа типа non-type интегрального типа, он не является допустимым аргументом-шаблоном для шаблона-типа типа типа типа. Тем не менее, оба (int*)0 и nullptr являются допустимыми шаблонами-аргументами для нетипового шаблона-параметра типа "указатель на int". - конечная нота]