3

Why I have to use the default value (::type = 0) in this std::enable_if usage?

I see examples, where it works without it. for example https://foonathan.net/blog/2015/11/30/overload-resolution-4.html

#include<iostream>
#include<type_traits>

template <typename T,
          typename std::enable_if<std::is_integral<T>::value, T>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff integral\n";
}

template <typename T,
          typename std::enable_if<std::is_class<T>::value, T>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff class\n";
}

int main()
{
    do_stuff(32);
    return 0;
}

I got error message:

temp.cpp:6:6: note:   template argument deduction/substitution failed:
temp.cpp:18:13: note:   couldn't deduce template parameter ‘<anonymous>’

It should be deduced to

template <template T, int>
void do_stuff(T t)

which is a valid code. What I'm doing wrong? (gcc version 7.4.0)

MiCha
  • 33
  • 4
  • 1
    In the example you cite, the invocation of `enable_if` **is** the default argument. It looks like this: `typename = std::enable_if<...>::type`. It's the template type parameter (with its name omitted) given a default value. In your code, you have a non-type template parameter of type `std::enable_if<...>::type`, with no way to deduce it. – Igor Tandetnik May 19 '19 at 16:04
  • @IgorTandetnik When I do this I get: temp.cpp:13:6: error: redefinition of ‘template void do_stuff(T)’ void do_stuff(T t) { ^~~~~~~~ temp.cpp:7:6: note: ‘template void do_stuff(T)’ previously declared here void do_stuff(T t) { – MiCha May 19 '19 at 16:16
  • The example in the article doesn't compile either. Probably time to look for a better article. – Igor Tandetnik May 19 '19 at 16:22
  • @IgorTandetnik. It's Jonathan Müller's post (https://twitter.com/foonathan). It should work :) – MiCha May 19 '19 at 16:26
  • Well, maybe it should, but [it does not](https://rextester.com/VARD8825) – Igor Tandetnik May 19 '19 at 16:27
  • 1
    @DanielLangr. Yes, this is the working example, I'm asking why ::type = 0 has to be there? Try to remove it. – MiCha May 19 '19 at 16:29
  • @MiCha I see, misinterpreted your question (it would be better to post a code that produces the error). Anyway, AnT's answer explains the problem. – Daniel Langr May 19 '19 at 17:06
  • In the second overload for class-type `T`, note the `= 0` default template argument won't compile if class `T` can't be copy-initialized from a `0`. This is why you'll see some code use instead `template ::type* = nullptr>`. In that usage, the type resulting from `enable_if` is `void` if _expr_ is true, so the last template parameter type is always `void*` no matter what the other template parameter(s) are. – aschepler May 19 '19 at 20:51

2 Answers2

4

The compiler clearly told you what the problem is: in your template declaration you specified an extra template non-type parameter that cannot be deduced. How do you expect the compiler to deduce a proper value for that non-type parameter? From what?

This is exactly why the above technique for using std::enable_if requires a default argument. This is a dummy parameter, so the default argument value does not matter (0 is a natural choice).

You can reduce your example to a mere

template <typename T, T x> 
void foo(T t) {}

int main()
{
  foo(42);
}

Producing

error: no matching function for call to 'foo(int)'
note:   template argument deduction/substitution failed:
note:   couldn't deduce template parameter 'x'

The compiler can deduce what T is (T == int), but there's no way for the compiler to deduce the argument for x.

Your code is exactly the same, except that your second template parameter is left unnamed (no need to give a name to a dummy parameter).


Judging by your comments, you seem to be confused by the presence of keyword typename in the declaration of the second parameter in your code, which makes you believe that the second parameter is also a type parameter. The latter is not true.

Note, that in the second parameter's declaration keyword typename is used in a completely different role. This keyword simply disambiguates the semantics of

std::enable_if<std::is_class<T>::value, T>::type

It tells the compiler that nested name type actually represents a name of a type, not of something else. (You can read about this usage of typename here: Why do we need typename here? and Where and why do I have to put the "template" and "typename" keywords?)

This usage of typename does not turn the second parameter of your template into a type parameter. The second parameter of your template is still a non-type parameter.

Here's another simplified example that illustrates what happens in your code

struct S { typedef int nested_type; };

template <typename T, typename T::nested_type x>
void bar(T t)
{}

int main()
{
  S s;
  bar<S, 42>(s);
}

Note that even though the declaration of the second parameter begins with a typename, it still declares a non-type parameter.

Deduplicator
  • 41,806
  • 6
  • 61
  • 104
AnT
  • 291,388
  • 39
  • 487
  • 734
  • it seems that you are right. I'm still missing there something. My understanding is that if compiler deduces the first T to int, then also the second T is int, so -> x is int. What do you mean by "arguments for x?", type ? Thx for clarification – MiCha May 19 '19 at 17:07
  • 2
    @MiCha: You seem to be missing the fact that in both cases the first template parameter is a **type** parameter, while the second is a **non-type** parameter (a "value" parameter). In my example parameter `T` stands for `int`, but `x` does not stand for `int`. `x` is a **value** parameter, it stands for **some value** of type `int`. What value is the compiler supposed to use? The compiler does not know. – AnT May 19 '19 at 18:16
  • @ AnT Yes, you are right, but that's the **difference** between your and my example. In my case also the second template parameter is type (keyword typename) and the enable_if::type is alias for T. So if T is int then ::type is int, so why the type parameter needs also the **default value**? Thx – MiCha May 19 '19 at 19:05
  • @MiCha: No, no, no, no, no. Your example is exactly the same as mine. You simply misinterpreted the meaning of the word `typename` in the second parameter. No, it **does not** mean that this is a type parameter. Your second parameter is a value parameter, just like mine. See the updated answer. – AnT May 19 '19 at 19:36
  • That's it! Thank you ! – MiCha May 19 '19 at 20:20
  • @MiCha As an aside, avoid using a dummy-parameter when you can use the return-type, to avoid pushing it into the decorated name, in case that ends up in a symbole-table. – Deduplicator May 19 '19 at 20:54
1

I cannot reproduce your error; anyway I get an error calling do_stuff() with a class. By example

do_stuff(std::string{"abc"})

This is because do_stuff() become do_stuff<std::string, std::string = 0>() and a template value can't be of type std::string (and not with default value zero).

Suggestion: rewrite your functions imposing int as type for the value in second position

template <typename T, // .....................................VVV  int, not T
          typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff integral\n";
}

template <typename T, // ..................................VVV  int, not T
          typename std::enable_if<std::is_class<T>::value, int>::type = 0>
void do_stuff(T t) {
    std::cout << "do_stuff class\n";
}

This way, calling do_stuff(std::string{"abc"}), you enable do_stuff<std::string, int = 0>() that is acceptable.

max66
  • 60,491
  • 9
  • 65
  • 95
  • Yes, the best is: `template ::value, T>::type* = nullptr>`. The question is, why this nullptr have to be there ? – MiCha May 19 '19 at 16:22
  • @MiCha Well, where else is the value of this non-type template parameter supposed to come from? You basically have `template void do_stuff(T);` - the compiler needs the value for `x` – Igor Tandetnik May 19 '19 at 16:24
  • @MiCha - There is no needs of use `T` (directly or as a pointer); `int = 0` works perfectly. Anyway, the `= nullptr` (or `= 0`, in my example) is necessary to avoid to explicit it; otherwise you have to call `do_stuff(42)` instead of `do_stuff(42);`. – max66 May 19 '19 at 16:41