Barry
Barry

Reputation: 303890

Recursive variadic function template

I want to write a class method that takes a template parameter pack, but zero arguments, and "iterate" over the types:

struct Bar {
    template <typename T, typename... Ts>
    void foo() {
        // something with T that involves Bar's members
        foo<Ts...>();
    }
};

What is the preferred way to implement this?

Upvotes: 7

Views: 945

Answers (3)

Jarod42
Jarod42

Reputation: 218268

You may use the following:

struct Bar {
    template <typename... Ts>
    void foo() {
        int dummy[] = {0 /*Manage case where Ts is empty*/,
                       (bar<Ts>(), void() /* To avoid overload `operator,` */, 0)...};
        (void) dummy; // suppress warning for unused variable.
    }

    template <typename T>
    void bar()
    {
        // something with T that involves Bar's members
    }

};

In C++17, it can be simplified with Folding expression:

struct Bar {
    template <typename... Ts>
    void foo() {
        (static_cast<void>(bar<Ts>()), ...);
    }

    template <typename T>
    void bar()
    {
        // something with T that involves Bar's members
    }

};

Upvotes: 3

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275896

template<class...Fs>
void do_in_order(Fs&&...fs) {
  int _[]={0, ( std::forward<Fs>(fs)(), void(), 0 )...};
  (void)_;
}

hides the syntax required to execute a pack of function objects in left to right order.

Then:

struct Bar {
  template <class... Ts>
  void foo() {
    do_in_order([&]{
      using T = Ts;
      // code 
    }...);
  }
};

and in a conforming compiler, we will run the // code with T being each type from left to right.

Note that some compilers claiming to be C++11 compilers may fail to compile the above.

The advantage of this technique is that it hides the nasty "expand and evaluate templates" code within a function with a clear name. You write do_in_order once, and it usually suffices for almost every use of that array-expansion trick.

There are a two important reasons to use this kind of esoteric syntax instead of the "more simple" recursive solutions.

First, it makes things easier for the optimizer. Optimizers sometimes give up after a pile of recursive calls.

Second, the sum of the lengths names of the function signatures for the traditional recursive functions grow at O(n^2). If you use helper types, the total length of the names is also O(n^2). Unless you are careful, this can cause compile time, link time, and binary size bloat.

In C++1z there are plans for some "fold" syntax that may make the esoteric parts of the above less esoteric.

Upvotes: 3

MadScientist
MadScientist

Reputation: 3460

I like overloaded functions and using a typelist:

#include <iostream>
#include <typeinfo>

template <typename ...Ts> struct typelist { };

void foo_impl(typelist<> )
{
  // we are finished
}

template <typename T, typename ...Ts>
void foo_impl(typelist<T, Ts...> )
{
  std::cout << typeid(T).name() << ", ";
  foo_impl(typelist<Ts...>{});
}



template <typename ...Ts>
void foo()
{
  std::cout << "called with <";
  foo_impl(typelist<Ts...>{});
  std::cout << ">" << std::endl;
}


int main()
{
  foo<int, char, float>();
}

Upvotes: 0

Related Questions