2

I'm currently studying enable_if and I have this code:

//template<typename T, typename = int/double/float/...> //not working properly
template<typename T, typename = void> //works fine
struct test{
    void func(){
        cout << "default" << endl;
    }
};

template<typename T>
struct test<T, typename std::enable_if<(sizeof(T) <= 1)>::type>{
    void func(){
        cout << "called" << endl;
    }
};

int main() {
    test<char> objs1;
    objs1.func(); //called
    test<int> objs2;
    objs2.func(); //default
}

I don't know the reason why I have to set the second parameter's default value as void. If I set it to other values like int or float or double, both objs1.func(); and objs2.func(); will print default. What is the reason?

Aamir
  • 1,974
  • 1
  • 14
  • 18
Kevin eyeson
  • 375
  • 4
  • 8
  • 1
    Does this answer your question? [enable\_if for class template specialization with argument other than void](https://stackoverflow.com/questions/72103700/enable-if-for-class-template-specialization-with-argument-other-than-void) – j6t Jan 18 '23 at 07:00

2 Answers2

2

So, std::enable_if<...>::type is, in fact, a type. Because you didn't specify what the type should be, you just specified the condition for which it exists at all, the default is void.

Let's look at your second version of the template. If sizeof(T) <= 1, you provide a template specialization for test<T, void>. Otherwise, the substitution fails and you provide nothing.

Now let's consider what happens when you just write test<char> objs1;. In your original version, because the default value for the unnamed second template parameter was void, this means objs1 is actually of type test<char, void>. And we actually have a specialization for test<char, void>, because sizeof(char) <= 1 is true.

However, if you change the default value of the unnamed second template parameter, we get a very different situation. Say you make the default value int instead of void. Then test<char> objs1; is actually declaring an object of type test<char, int>. We have a specialization defined for test<char, void>... But we aren't trying to create a test<char, void>, we're trying to create the separate type test<char, int>. So the fact that the condition of the enable_if is true is neither here nor there and we get the default definition of test.

Nathan Pierson
  • 5,461
  • 1
  • 12
  • 30
  • Thank you so much! This is a really good explanation. So I want to double check is: If I set the defaule value to `int` but do nothing on the `enable_if`,when I call`test objs1;`, it is actually `test objs1;`. But for the `enable_if`, although we fulfill the requirement, but because I didn't provide the second parameter for `enable_if`, so it will set it to `void`. At this time, the main template is ``. So obviously, only the main template is fulfill the requirement. – Kevin eyeson Jan 18 '23 at 04:47
  • 1
    Yes. You're creating an object of type `test`, aka `test`. The only implementation you have of `test` is the default one. If you explicitly create an object of type `test`, you'll get the specialized behavior (see [here](https://godbolt.org/z/4hj5K6aPK)). But if you make it so the default value of the original parameter matches the result type that your `enable_if` yields, it saves you from having to spell out `void` explicitly. – Nathan Pierson Jan 18 '23 at 04:54
1

The technique that is being used is SFINAE implemented via partial template specialization. In order to have multiple different types of tests depending on the characteristics of T we need to have the SFINAE expression in the template parameter list. Since class cannot be overloaded you build an "overload set" by creating a main default template and then partial specializations for all of the different cases. To do that the main template needs to have two parameters, T and the type that enable_if will resolve to. We default that second parameter to void so that it does not need to be specified by the caller to get the main template.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402