Search code examples
c++c++14variadic-templatesvariadic-functions

C++ variadic template empty packet case


I'm very new to meta-programming and I was trying to play around with variadic templates. I've searched a lot on StackOverflow and came across multiple solutions for my problem but I couldn't seem to make them work. The code I have is the following:

template <typename TA, typename... TB>
class A
{
public:
    inline long long f()
    {
        // 6 is just a placeholder, the value 
        // comes from a dummy object which is a class member
        return g<TB...>(6); 
    }

[...]

private:
    template <typename Head, typename Head2, typename ...Tail>
    inline long long g(long long x)
    {
        return (&Head::instance())->external_function(g<Head2, Tail...>(x));
    }

    template<typename Head>
    inline long long g(long long x) {
        return (&Head::instance())->external_function(x);
    }

    inline long long g(long long x) {
        return x;
    }

[...]

Where TB can be 0 or more elements. So what I was trying to achieve is to call a method external_function in cascade which I know every element inside TB has defined. It works if TB has one element, but if TB is empty, what I get is that I have no function matching g<>.

Then I tried to redefine the three overloaded functions as follows

template <typename Head, typename ...Tail>
inline long long g(long long x)
{
    return (&Head::instance())->external_function(g<Tail...>(x));
}

template<typename Head>
inline long long g(long long x) {
    return (&Head::instance())->external_function(x);
}

template<typename = void>
inline long long g(long long x) {
    return x;
} 

But still no luck.

So what I tried again was to use two specialised functions, one that accepts one or more arguments and the other that is called only when there are zero arguments

template <typename Head, typename ...Tail>
inline long long g(long long x)
{
    return (&Head::instance())-> external_function(g<Tail...>(x));
}


template<typename... Tail>
inline long long g(long long x) {
    return x;
}

But again, what I get is that this last solution only works if TB is empty. If it contains at least one element I get an error about an ambiguous call.

I'm getting lost with variadic functions and their base cases also because their usage has changed a lot during the years with C++11, C++17 and C++20. I've seen that fold expressions can solve the problem and make the code more readable, but I had no luck trying to use them for cascade calls.

Really hope someone can help me shed light on this.

Edit: For project reasons, I'm stuck with C++14 and below.


Solution

  • With C++14, you can achieve what you need by using partial class specialization:

    template <typename TA, typename... TB>
    class A
    {
    public:
        inline long long f()
        {
            // 6 is just a placeholder, the value 
            // comes from a dummy object which is a class member
            return g_impl<TB...>::call(6LL); 
        }
    
    private:
    
        // base specialization, fallback for 0 elements
        template<typename... Ts>
        struct g_impl
        {
            static inline long long call(long long x)
            {
                std::cout << "empty typelist case\n";
                return x;
            }
        };
    
        // 1 or more elements
        template< typename Head, typename... Tail>
        struct g_impl<Head, Tail...>
        {
            static inline long long call(long long x)
            {
                std::cout << (sizeof...(Tail) + 1) << " types case\n";
                return (&Head::instance())->external_function(g_impl<Tail...>::call(x));
            }
        };
    };
    

    Let's test with the following identical classes:

    struct Test1
    {
        long long external_function(long long x){std::cout << "Test1::external_function\n"; return 42;}
        static Test1& instance()
        {
            static Test1 self;
            return self;
        }
    };
    
    struct Test2
    {
        long long external_function(long long x){std::cout << "Test2::external_function\n"; return 42;}
        static Test2& instance()
        {
            static Test2 self;
            return self;
        }
    };
    
    struct Test3
    {
        long long external_function(long long x){std::cout << "Test3::external_function\n"; return 42;}
        static Test3& instance()
        {
            static Test3 self;
            return self;
        }
    }
    

    And call it from main like so:

    int main()
    {
        std::cout << "---\n";
        std::cout << "Testing A<Test1>\n";
        A<Test1> a;
        a.f();
    
        std::cout << "---\n";
        std::cout << "Testing A<Test1, Test2>\n";
        A<Test1, Test2> b{};
        b.f();
    
        std::cout << "---\n";
        std::cout << "Testing A<Test1, Test2, Test3>\n";
        A<Test1, Test2, Test3> c;
        c.f();
        std::cout << "---\n";
    }
    

    Output:

    ---
    Testing A<Test1>
    empty typelist case
    ---
    Testing A<Test1, Test2>
    1 types case
    empty typelist case
    Test2::external_function
    ---
    Testing A<Test1, Test2, Test3>
    2 types case
    1 types case
    empty typelist case
    Test3::external_function
    Test2::external_function
    ---
    

    Live Demo (clang, gcc, and msvc)


    In C++20 we can squeeze this all into a single recursive template function:

    template<typename... Ts>
    inline long long g(long long x)
    {
        // empty typelist
        if constexpr(sizeof...(Ts) == 0)
        {
            std::cout << "Empty typelist case\n";
            return x;
        }
        else
        {
            // 1 or more in typelist
            std::cout << sizeof...(Ts) << " in typelist\n";
            auto callable = [=]<class Head, class... Tail>
            {
                return Head::instance().external_function(g<Tail...>(x));
            };
            return callable.template operator()<Ts...>();
        }
    }
    

    C++20 Demo


    EDIT: Per OP's request, they'd also like to reverse the parameter pack before the calls, which requires some boilerplate to create the notion of a typelist (empty class) and perform operations on it to reverse it, and then later extract the types:

    template<class... Ts>
    struct typelist{};
    
    template<class... Ts, class... Us>
    auto concat(typelist<Ts...>, typelist<Us...>) -> typelist<Ts..., Us...>;
    
    auto reverse(typelist<>) -> typelist<>;
    
    template<class Head, class... Tail>
    auto reverse(typelist<Head, Tail...>)
        -> decltype(
            concat(
                   reverse(std::declval<typelist<Tail...>>()),
                   std::declval<typelist<Head>>()
                   ) // concat
          ); // decltype
    
    template<class... Ts>
    using reversed_typelist = decltype(reverse(std::declval<typelist<Ts...>>()));
    

    Then we add another level of indirection on calling f():

    public:
        inline long long f()
        {
            // 6 is just a placeholder, the value 
            // comes from a dummy object which is a class member
            return f_impl(reversed_typelist<TB...>{}, 6LL); 
        }
    
    private:
    
        template<class... Ts>
        inline long long f_impl(typelist<Ts...>, long long x)
        {
            return g_impl<Ts...>::call(x);
        }
    

    Now that we've reversed the parameter pack, the output demonstrates the calls in reverse order as before:

    ---
    Testing A<Test1>
    empty typelist case
    ---
    Testing A<Test1, Test2>
    1 types case
    empty typelist case
    Test2::external_function
    ---
    Testing A<Test1, Test2, Test3>
    2 types case
    1 types case
    empty typelist case
    Test2::external_function
    Test3::external_function
    ---
    

    C++14 demo with reversed parameter pack