8

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
Andy Brown
  • 11,766
  • 2
  • 42
  • 61
  • 2
    Not sure what you are trying to achieve: your `recinit` function is infinitely recursive, or am I mistaken? how do you expect to call it from `init()`, with what arguments? – Andy Prowl Mar 08 '13 at 14:14
  • 3
    Stop using an `init` method and use a constructor. – Nicol Bolas Mar 08 '13 at 14:21

3 Answers3

4

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.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • Even though I'm sure all the voted responses are good, this is the one that most fits my requirements. I plugged it in to my hierarchy and it works perfectly and rodrigo you were correct to assume that init() is a non-static member function. I learned something new today, and on a Friday too. Good stuff. – Andy Brown Mar 08 '13 at 15:49
  • @AndyBrown: Re-reading my answer, the `static_cast` is not really needed, because `T` must be a base class of `F`. Removed, code is always nicer without casts. – rodrigo Mar 08 '13 at 17:58
3

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
}
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • This doesn't collect the return values from the features' `init`s, though. Should be more like `fold();`. – Xeo Mar 08 '13 at 14:20
  • 2
    `auto il = {Features::init()...}; return std::all_of(il.begin(), il.end(), [](bool b){ return b;});` :) – Xeo Mar 08 '13 at 14:38
  • Correct me if I'm wrong, but I believe the OP's code features "short circuit" evaluation of the `init` functions whereas your code evaluates all `init` functions before examining their results. I don't know if the distinction is important here but it could potentially lead to different outcomes. – Andrew Durward Mar 08 '13 at 14:40
  • @Xeo: Nice one. It still has to invoke all the init functions though. Going post a solution that doesn't. – Andy Prowl Mar 08 '13 at 14:40
  • @AndrewDurward: Correct. I'm going to post a solution that exploits the short circuit. – Andy Prowl Mar 08 '13 at 14:41
  • @Andrew Why do you think `all_of` isn’t short-circuited? This would be a decidedly odd implementation. – Konrad Rudolph Mar 08 '13 at 14:44
  • @KonradRudolph: `all_of` may be short-circuited, but you're only passing a list of `bool`s to it - which you get from invoking all `init` functions. – Xeo Mar 08 '13 at 14:45
  • @Xeo Er, well, the same is true in Andy’s current implementation. Obviously you’d have to construct the `initializer_list` in such a way that the values aren’t evaluated yet. /EDIT Ah, just noticed the time stamp of the comments and the last edit of the answer. I thought Andy’s answer came *after* the comments. – Konrad Rudolph Mar 08 '13 at 14:47
  • @KonradRudolph: I posted a solution which exploits short-circuiting. The one based on `initializer_list` favored simplicity. – Andy Prowl Mar 08 '13 at 14:48
  • @Andy Yes, sorry; somehow I thought Andrew was referring to Xeo’s comment. Now, there’s nothing wrong with the current solution but I’d be very interested in seeing a solution that is short-circuited and yet uses `all_of`. Without the overhead of `std::function`, obviously. ;-) /EDIT Forget it, that requires polymorphic lambdas. Sh*t. – Konrad Rudolph Mar 08 '13 at 14:50
  • Sorry, one last comment. your code fails if `Features...` only contains a single type. The recursion anchor for `combine_and` should accept a single (or alternatively no) argument. – Konrad Rudolph Mar 08 '13 at 14:54
  • @KonradRudolph: Good observation, I'll fix it. And I'm trying to figure out a solution which is short-circuited and uses `std::all_of`, but it doesn't seem easy :) – Andy Prowl Mar 08 '13 at 14:56
  • @KonradRudolph: OK, have it – Andy Prowl Mar 08 '13 at 15:03
  • @Xeo: Is it ok to cast a `Base::*` to a `Derived::*`, like you do? – Luc Touraille Mar 08 '13 at 15:04
  • @Xeo: Damn, you were faster :-) – Andy Prowl Mar 08 '13 at 15:04
  • @Andy Argh, C-style casts again. And two redundant `combine_and` overloads. Okay, I’ll show myself to the door now, sorry for the comment spam. :-p … Good discussion though. – Konrad Rudolph Mar 08 '13 at 15:05
  • @KonradRudolph: Yes, I know, I need to stop with those. I've edited. And will remove the redundant overload ;-) – Andy Prowl Mar 08 '13 at 15:07
  • @Xeo: Well, it turns out [it is indeed ok](http://stackoverflow.com/questions/60000/c-inheritance-and-member-function-pointers). – Luc Touraille Mar 08 '13 at 15:07
  • @LucTouraille: Yes, the member functions of a base class will always be there in the derived class. This may seem a bit strange, considering that you can't just cast `B*` to `D*`, but `T B::*` to `T D::*`. Also note that the inverse doesn't work. :) No `T D::*` to `T B::*`. – Xeo Mar 08 '13 at 15:10
  • @Xeo your sample code doesn't seem to work on Intel ICC v15. It gets the offsets wrong for the base classes. – BlamKiwi Mar 25 '15 at 10:58
2

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214