Search code examples
c++c++11templatesvariadic-templatespartial-specialization

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?


Solution

  • 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