Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specializing a template method with enable_if

I'm writing a template class that stores a std::function in order to call it later. Here is the simplifed code:

template <typename T>
struct Test
{
    void call(T type)
    {
        function(type);
    }
    std::function<void(T)> function;
};

The problem is that this template does not compile for the void type because

void call(void type)

becomes undefined.

Specializing it for the void type doesn't alleviate the problem because

template <>
void Test<void>::call(void)
{
    function();
}

is still incompatible with the declaration of call(T Type).

So, using the new features of C++ 11, I tried std::enable_if:

typename std::enable_if_t<std::is_void_v<T>, void> call()
{
    function();
}

typename std::enable_if_t<!std::is_void_v<T>, void> call(T type)
{
    function(type);
}

but it does not compile with Visual Studio:

error C2039: 'type' : is not a member of 'std::enable_if'

How would you tackle this problem?

like image 344
Mark Morrisson Avatar asked Nov 28 '17 00:11

Mark Morrisson


3 Answers

Specialize the whole class:

template <>
struct Test<void>
{
    void call()
    {
        function();
    }
    std::function<void()> function;
};
like image 138
Jarod42 Avatar answered Nov 03 '22 19:11

Jarod42


SFINAE doesn't work over (only) the template parameters of the class/structs.

Works over template methods whit conditions involving template parameters of the method.

So you have to write

   template <typename U = T>
   std::enable_if_t<std::is_void<U>::value> call()
    { function(); }

   template <typename U = T>
   std::enable_if_t<!std::is_void<U>::value> call(T type)
    { function(type); } 

or, if you want to be sure that U is T

   template <typename U = T>
   std::enable_if_t<   std::is_same<U, T>::value
                    && std::is_void<U>::value> call()
    { function(); }

   template <typename U = T>
   std::enable_if_t<std::is_same<U, T>::value
                    && !std::is_void<U>::value> call(T type)
    { function(type); } 

p.s.: std::enable_if_t is a type so doesn't require typename before.

p.s.2: you tagged C++11 but your example use std::enable_if_t, that is C++14, and std::is_void_v, that is C++17

like image 2
max66 Avatar answered Nov 03 '22 20:11

max66


If you don't stick to use void, and your intention is to actually able to use Test without any parameters, then use variadic template:

template <typename ...T>
struct Test
{
    void call(T ...type)
    {
        function(type...);
    }
    std::function<void(T...)> function;
};

This way, you can have any number of parameters. If you want to have no parameters, use this:

Test<> t;
t.call();

So this is not exactly the syntax you wanted, but there is no need for specialization, and this solution is more flexible, because supports any number of parameters.

like image 2
geza Avatar answered Nov 03 '22 19:11

geza