Search code examples
c++variadic-templatesvariadic-functionstemplate-meta-programmingfunction-templates

C++ Variadic functions with no argument


I have multiple classes (Foo and Bar here for simplicity)

struct Bar {};
struct Foo {};

and a function that takes a single template parameter and does something based on that type:

template <typename T>
constexpr void doSomething() { cout << "Am I a Foo? " << is_same<T,Foo>::value << endl; }

In my code, I am given a template parameter pack of Foos and Bars, and I am supposed to call the doSomething() function on each one of them (I do not care about the order in which the functions are executed).

doStuff<Foo, Bar, Bar>(); // --> True / False / False

So far, the only solution I could come up with is this:

template <typename... Ts>
class Doer;

template <>
struct Doer <> {
    static constexpr void doStuff() {}
};

template <typename Head, typename... Tail>
struct Doer <Head, Tail...> {
    static constexpr void doStuff() {
        doSomething<Head>();
        Doer<Tail...>::doStuff();
    }
};

template <typename... Ts>
constexpr void doStuff() {
    return Doer<Ts...>::doStuff();
}

doStuff<Foo, Bar, Bar>(); // --> True / False / False

It works, but I find it rather messy. I had to use a class template with partial specializations because function templates only support full specialization. I also tried

constexpr void doStuff() { }

template <typename Head, typename... Tail>
constexpr void doStuff() {
    doSomething<Head>();
    doStuff<Tail...>();   // --> Compile Error
}

but the compiler fails because it can't figure out that doStuff<>() is actually doStuff(). If I have arguments in my variadic functions, then the compiler is smart enough to resolve this conflict as it applies template type deduction:

constexpr void doStuff() { }

template <typename Head, typename... Tail>
constexpr void doStuff(Head arg, Tail... args) {
    doSomething<Head>();
    doStuff(args...);
}

Foo f1;
Bar b1, b2;
doStuff<Foo, Bar, Bar>(f1, b1, b2); // --> True / False / False

Am I missing something? Is there a way to get my variadic function working without using function parameters or class templates?


Solution

  • but the compiler fails because it can't figure out that doStuff<>() is actually doStuff().

    What about

    template <int = 0>
    constexpr void doStuff() { }
    
    template <typename Head, typename... Tail>
    constexpr void doStuff() {
        doSomething<Head>();
        doStuff<Tail...>();
    }
    

    ?

    I mean: if the problem is that, at the end, the template variadic list is empty, transform the ground case in a template version with a template parameter (completely different: integer instead of types) with a default value.

    So, when Tail... is empty, the call doStuff<Tail...>(), that is doStuff<>(), matches doStuff<0>() (considering the default value in first function) so call the ground case.

    Anyway: if you can use C++17, you can avoid recursion and, using the power of comma operator together with template folding, you can simply write

    template <typename... Ts>
    constexpr void doStuff() {
        (doSomething<Ts>(), ...);
    }
    

    In C++14 you can simulate template folding as follows

    template <typename... Ts>
    constexpr void doStuff() {
        using unused = int[];
    
        (void) unused { 0, ((void)doSomething<Ts>(), 0)... };
    }
    

    The preceding solution works also with C++11 but not as constexpr (but also doSomething() can't be constexpr in C++11).

    Taking in count that you don't care about the order in which the functions are executed, I propose a C++11 solution that maintain constexpr and it's based on template pack expansion in a fake-function call (or maybe not fake... see you).

    But this require that doSomething() is constexpr (so, in C++11, can't be void) and also doStuff() can't be void

    #include <iostream>
    
    template <typename T> 
    constexpr std::size_t doSomething ()
     { return sizeof(T); }
    
    template <typename ... Ts>
    constexpr int fakeFunc (Ts const & ...)
     { return 0; }
    
    template <typename ... Ts>
    constexpr int doStuff ()
     { return fakeFunc( doSomething<Ts>()... ); }
    
    
    int main()
     {
       constexpr int a { doStuff<char, short, int, long, long long>() };
    
       (void)a;
     }