Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial template specialization for variadic template where [Args...] is empty

I have a class, Delegate, declared like this:

template<typename T> class Delegate;

template<typename R, typename... Args>
class Delegate<R(Args...)>
{ /*...*/ };

It can be instantiated for a function returning a ReturnType and taking no arguments as a Delegate<ReturnType()>. I've run into an issue that requires me to specialize the class' () operator for this case, but haven't been able to figure out how to coerce the compiler doing so without a compiler error.

I have the following function:

template <typename R, typename... Args>
R Delegate<R(Args...)>::operator()(Args... args)
{ /*...*/ }

Adding the following specialization, I get an an error saying invalid use of incomplete type 'class Delegate<R()>':

template <typename R>
R Delegate<R()>::operator()()
{ /*...*/ }

but I can't simply replace Args... with void either, as far as I've been able to tell... What is the proper procedure here, and (if this question applies and you are feeling extra helpful) why?

like image 924
Conduit Avatar asked Dec 15 '22 18:12

Conduit


1 Answers

Your attempt with using R Delegate<R()>::operator()() to specialize even more the member function of a partial specialization of a class template fails due to §14.5.5.3 [temp.class.spec.mfunc]:

1 The template parameter list of a member of a class template partial specialization shall match the template parameter list of the class template partial specialization.

In other words:

template <typename R>
R Delegate<R()>::operator()() { /**/ }

is actually a specialization of operator() of your primary template:

template <typename T>
class Delegate;

and since it's an incomplete type, you end up with the error. The possible workarounds are:

Option #1

Specialize the entire class and reimplement all the members of that class:

template <typename T>
class Delegate;

template <typename R, typename... Args> // partial specialization for non-empty Args
class Delegate<R(Args...)>
{
    R operator()(Args...) { return {}; }
};

template <typename R> // partial specialization for empty Args
class Delegate<R()>
{
    R operator()() { return {}; }
};

DEMO 1

Option #2

Use a one more delegate class that is specialized:

#include <utility>

template <typename T>
struct Impl;

template <typename R, typename... Args>
struct Impl<R(Args...)>
{
    static R call(Args&&...) { return {}; }
};

template <typename R>
struct Impl<R()>
{
    static R call() { return {}; }
};

template <typename T>
class Delegate;

template <typename R, typename... Args>
class Delegate<R(Args...)>
{
    R operator()(Args... args)
    {
        return Impl<R(Args...)>::call(std::forward<Args>(args)...);
    }
};

DEMO 2

Option #3

Use some ugly SFINAE:

#include <type_traits>

template <typename T>
class Delegate;

template <typename R, typename... Args>
class Delegate<R(Args...)>
{
    template <typename T = R>
    typename std::enable_if<sizeof...(Args) != 0 && std::is_same<T,R>{}, R>::type
    operator()(Args...) { return {}; }

    template <typename T = R>
    typename std::enable_if<sizeof...(Args) == 0 && std::is_same<T,R>{}, R>::type
    operator()() { return {}; }
};

DEMO 3

Option #4

Inherit from a specialized class template, possibly utilizing the CRTP idiom:

template <typename T>
class Delegate;

template <typename T>
struct Base;

template <typename R, typename... Args>
struct Base<Delegate<R(Args...)>>
{
    R operator()(Args...)
    {
        Delegate<R(Args...)>* that = static_cast<Delegate<R(Args...)>*>(this);
        return {};
    }
};

template <typename R>
struct Base<Delegate<R()>>
{
    R operator()()
    {
        Delegate<R()>* that = static_cast<Delegate<R()>*>(this);
        return {};
    }
};

template <typename R, typename... Args>
class Delegate<R(Args...)> : public Base<Delegate<R(Args...)>>
{
    friend struct Base<Delegate<R(Args...)>>;
};

DEMO 4

like image 173
Piotr Skotnicki Avatar answered Dec 17 '22 06:12

Piotr Skotnicki