Why `void* = 0` and `void* = nullptr` makes the difference?

  • A+
Category:Languages

I was playing with SFINAE and found behavior I cannot explain.

This compiles fine:

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) {} 

While this (nullptr replaced with 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) {} 

gives me a compile error:

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 expands to void when there are no substitution failures, so I will have something like void* = 0 among the list of template parameters. Why does it break compilation?..

 


Default template arguments follow their own conversion rules, which are stricter. Conversion of 0 to a pointer type in particular, is not applied.

See [temp.arg.nontype]/5.2 (emphasis mine):

for a non-type template-parameter of type pointer to object, qualification conversions ([conv.qual]) and the array-to-pointer conversion ([conv.array]) are applied; if the template-argument is of type std::nullptr_t, the null pointer conversion ([conv.ptr]) is applied.

[ Note: In particular, neither the null pointer conversion for a zero-valued integral constant expression ([conv.ptr]) nor the derived-to-base conversion ([conv.ptr]) are applied. Although 0 is a valid template-argument for a non-type template-parameter of integral type, it is not a valid template-argument for a non-type template-parameter of pointer type. However, both (int*)0 and nullptr are valid template-arguments for a non-type template-parameter of type “pointer to int.” — end note ]

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: