Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template base class call forwarding

In pre-11 C++ I had something like this:

template<class T,class U,class V>
struct Foo : T,U,V {

  bool init() {

    if(!T::init() || !U::init() || !V::init())
      return false;

    // do local init and return true/false
  }
};

I'd like to convert this to C++11 variadic syntax to get the benefit of the flexible length argument list. I understand the concept of unpacking the template arg list using recursion but I just can't seen to get the syntax right. Here's what I've tried:

template<typename... Features>
struct Foo : Features... {

  template<typename F,typename... G>
  bool recinit(F& arg,G&& ...args) {

    if(!F::init())
      return false;

    return recinit<F,G...>(args...);
  }

  bool init() {
    // how to call recinit() from here?
  }
};

I would prefer the order of the calls to the base class init() functions to be left-to-right but it's not critical.

like image 376
Andy Brown Avatar asked Mar 08 '13 14:03

Andy Brown


3 Answers

This should work:

template<typename F, typename... T>
    struct recinit;

template<typename F>
    struct recinit<F> {
        static bool tinit(F *) {
            return true;
        }
    };
template<typename F, typename T, typename... G>
    struct recinit<F, T, G...> {
        static bool tinit(F *ptr)  {
            if (!ptr->T::init())
                return false;
            return recinit<F, G...>::tinit(ptr);
        }
    };

template<typename... Features>
struct Foo : Features... {

    bool init() {
        bool res = recinit<Foo, Features...>::tinit(this);
        //use res wisely
    }
};

Your problem is that you cannot write partial specializations of functions, only of classes/structs. And the auxiliary struct has to be outside of Foo or else it will get the template arguments from the enclosing struct, and that would be bad.

You don't say but I'm assuming that init is a non-static member function. If that is the case, the args arguments make little sense: all of them should be this! So just past this once and avoid the pack in the arguments. I tried passing this as a void* but that may be troublesome, so I just added an additional template argument to recinit that will be Foo.

And also, each time you do one recursive step remember to remove one parameter.

like image 142
rodrigo Avatar answered Oct 16 '22 17:10

rodrigo


Maybe you could try something like this:

template<typename... Features>
struct Foo : Features...
{
    bool init()
    {
        // Courtesy of Xeo :-)
        auto il = {(static_cast<bool (Foo::*)()>(&Features::init))...};
        return std::all_of(il.begin(), il.end(), 
            [this] (bool (Foo::*f)()) { return (this->*f)(); }
            );
    }
};

Here is an alternative, more verbose version that uses variadic templates:

template<typename... Features>
struct Foo : Features...
{
    bool init()
    {
        return combine_and((&Features::init)...);
    }

private:

    bool combine_and()
    {
        return true;
    }

    template<typename F>
    bool combine_and(F f)
    {
        return (this->*f)();
    }

    template<typename F1, typename... Fs>
    bool combine_and(F1 f1, Fs... fs)
    {
        return ((this->*f1)() && combine_and(fs...));
    }
};

Whichever solution you pick, this is how you could use it:

#include <iostream>

using namespace std;

struct A { bool init() { cout << "Hello " << endl; return true; } };
struct B { bool init() { cout << "Template " << endl; return true; } };
struct C { bool init() { cout << "World!" << endl; return true; } };

int main()
{
    Foo<A, B, C> f;
    bool res = f.init(); // Prints "Hello Template World!"
    cout << res; // Prints 1
}
like image 41
Andy Prowl Avatar answered Oct 16 '22 18:10

Andy Prowl


Your code has two problems. First, the rather mundane:

return recinit<F,G...>(args...);

You’ve already handled F, leave it off from the list of arguments.

return recinit<G...>(args...);

(Additionally you should probably perfectly-forward the arguments.)

Secondly, the code won’t compile because your recursion has an anchor at runtime but not at compile time. That is, the compiler will try unpacking the argument pack G ad infinitum. In order to prevent this you need to specialise the function for an empty template argument list.

like image 33
Konrad Rudolph Avatar answered Oct 16 '22 19:10

Konrad Rudolph