6
votes

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?

3
SFINAE works only for deduced template parameters.Kerrek SB

3 Answers

4
votes

Specialize the whole class:

template <>
struct Test<void>
{
    void call()
    {
        function();
    }
    std::function<void()> function;
};
2
votes

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

2
votes

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.