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?..
Nullptr cannot be defined as (void*)0 in the C++ standard library. Null pointer is a subtle example of Return Type Resolver idiom to automatically deduce a null pointer of the correct type depending upon the type it is assigning to.
(void*)0 is a null pointer constant, whose value is a null pointer of type void* , so by the semantics of parenthesized expressions ((void*)0) also has a value that is a null pointer of type void* .
As a reminder, since C++11, NULL can be either an integer literal with value zero, or a prvalue of type std::nullptr_t . Because of this ambiguity, I recommend switching exclusively to nullptr . nullptr will make your code less error-prone than relying on the implementation-defined NULL .
The null pointer is basically used in a program to assign the value 0 to a pointer variable of any data type. The void pointer, on the other hand, has no value assigned to it and we use it to store the addresses of other variables in the program- irrespective of their data types.
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
andnullptr
are valid template-arguments for a non-type template-parameter of type “pointer to int.” — end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With