23

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?..

Killzone Kid
  • 6,171
  • 3
  • 17
  • 37
Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • Because `0` is an `int` and `nullptr` is a `void *`. You can probably cast that `0` to `(void *)` to make it work. – tdk001 Jun 08 '18 at 17:33
  • 13
    @tdk001: *"`nullptr` is a `void *`"*, wrong, it is a `std::nullptr_t`. – Jarod42 Jun 08 '18 at 17:35
  • 4
    You can reduce the issue with `template void foo() {}`. clang give error *"null non-type template argument must be cast to template parameter type 'void *'"*. [Demo](http://coliru.stacked-crooked.com/a/8255a4cbde425295) – Jarod42 Jun 08 '18 at 17:41

1 Answers1

33

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 ]

Community
  • 1
  • 1
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • 4
    Good find. C++ has so many such non-obvious rules, It's frightening to think of all the ad hoc logic compiler writer must deal with. – R Sahu Jun 08 '18 at 18:03
  • 4
    I think the standard basically want to say "there are no templates in C, so at least *there* I don't have to be compliant with C" :) – rustyx Jun 08 '18 at 18:07
  • what was the motivation behind this decision? prevent undesirable implicit conversions or something? – kmdreko Jun 08 '18 at 18:19
  • 1
    And note this is a rule that the most recent MSVC still does not enforce even with the standard set to latest. – SoronelHaetir Jun 08 '18 at 18:29
  • @rustyx: some say that notes are not normative and cannot be used to prove anything. I had to edit/delete my answers couple of times because of this. So how to deal with such situations? – P.W Apr 18 '19 at 15:30
  • The note simply brings attention to the conversions which are not applied. It clarifies the rule just above. – rustyx Sep 04 '21 at 16:55