6

How can I iterate over all base classes of a variadic template class and call a function for each of them.

Here is a minimal example:

struct A { void foo() { std::cout << "A" << std::endl; } };
struct B { void foo() { std::cout << "B" << std::endl; } };
struct C { void foo() { std::cout << "C" << std::endl; } };

template<typename... U>
struct X : public U...
{
    void foo() {
        static_cast<U*>(this)->foo()...; // ??? should call `foo` for all `U`
    }
};

int main() {
    X<A,B,C> x;
    x.foo();
}
Danvil
  • 22,240
  • 19
  • 65
  • 88
  • Near-duplicate: [Can I tell which template instance class(es) my class is inheriting from?](http://stackoverflow.com/q/22882440/2644390) – iavr May 05 '14 at 00:31

2 Answers2

9

You can't normally without C++17's fold expressions. The ellipsis going there is not valid and the ellipsis going after the asterisk would create a list of pointer template arguments. For the appropriate pattern to be repeated, the ellipsis would have to be at the end of the statement, and that doesn't work here. I found this article to be a good resource for pack expansion.

Instead, there is a trick for it that doesn't require building up any recursive things:

int arr[] = {(static_cast<U*>(this)->foo(), 0)...};

This calls each function and then uses the result with the comma operator to produce the needed ints. Unfortunately, this might result in an unused variable warning. One minimal way around this is to use a std::array (or some class that can be initialized with an initializer list) and cast the result of creating an unnamed one of those to void (casting to void being a somewhat common technique for preventing the warning in general).

chris
  • 60,560
  • 13
  • 143
  • 205
7

Here is a way:

struct thru{template<typename... A> thru(A&&...) {}};

struct A { void foo() { std::cout << "A" << std::endl; } };
struct B { void foo() { std::cout << "B" << std::endl; } };
struct C { void foo() { std::cout << "C" << std::endl; } };

template<typename... U>
struct X : public U...
{
    void foo() { thru{(U::foo(), 0)...}; }
};

But if you care about the order of calls, watch out for the known gcc bug as discussed here.

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53
  • Good point about the order of evaluation. I believe the array in my answer guarantees the order to be left to right, but I can't quite remember. I know for sure something taking a `std::initializer_list` does. – chris May 05 '14 at 00:04
  • @chris Both are guaranteed by the standard. Gcc's bug is only for list-initialization, so your solution works correctly. I usually prefer a struct (like `_do`) that I know will ignore the arguments, though I guess the optimizer will ignore the array as well. – iavr May 05 '14 at 00:07
  • Ok, thanks. As a side note, you should pick a [different name](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier). – chris May 05 '14 at 00:19
  • @chris I am aware of these rules, but if I understand correctly, a single underscore is only a problem in the global namespace, and I usually never define anything there (the above was only an example). Right? – iavr May 05 '14 at 00:22
  • Yes, and even so, do remember the OP might not know them and that they would need to define it in a separate namespace. – chris May 05 '14 at 00:41
  • @chris Ok, good point. Changed to my second-favorite name, `thru`. Though most commonly known as `swallow` or `pass`. – iavr May 05 '14 at 00:53
  • same override `operator,` bug here I believe – Yakk - Adam Nevraumont May 05 '14 at 02:55