joe
joe

Reputation: 617

Call void function for each template type in a variadic templated function?

My goal is to write a simple generic function for registering converters for arbitrary C++ types. For simplicity I'll just print C++ type names. I'd like to be able to call my generic print_type_name function for any types, including multiple types at once (variadic):

template <typename T>
void print_type_name(void)
{
    std::cout << typeid(T).name() << std::endl;
}

This works fine for things like this:

print_type_name<int>();
print_type_name<std::string>();
print_type_name<std::vector<std::complex<float> > >();

However, I need to be able to call this function for each type in a variadic template, e.g. (when expanded):

print_type_name<int, std::string, std::vector<std::complex<float> > >();

Here's what I've come up with, but it's rather clunky:

template <typename ...TS>
void noop(TS... ts) { }

template <typename T>
int real_print_type_name(void) {
    std::cout << typeid(T).name() << std::endl;
    return 0;
}

template <typename ...TS>
void print_type_name(void) {
    noop(real_print_type_name<TS>()...);
}

Which allows for the following:

template <typename ...TS>
void other_function(void) {
    print_type_name<TS...>();
}

Notice the useless noop function and the int return type of real_print_type_name, both of which I had to add in order to expand the parameter pack. Is there a cleaner way of doing this?

Upvotes: 2

Views: 2253

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275500

Here is a helper function. It uses random dark magic, but its name is pretty clear:

void do_in_order() {}

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

or in C++17:

template<class...Fs>
void do_in_order( Fs&&...fs ) {
  (void(std::forward<Fs>(fs)())...);
}

(much nicer).

which hides any uglyness. It takes a set of void() callables and calls them left to right -- it does the tasks in order, like it says on the tin.

Then print_type_names becomes:

template<class...Ts>
void print_type_names() {
  do_in_order( print_type_name<Ts>... );
}

or

template<class...Ts>
void print_type_names() {
  do_in_order( [&]{
    std::cout << typeid(Ts).name() << std::endl;
  }... );
}

if you don't want to use the single print_type_name function and want to inline it.

Note that some non-conforming compilers complain about having an entire lambda be expanded in a ....

live example

Upvotes: 5

T.C.
T.C.

Reputation: 137315

template <typename ...TS>
void print_type_name() {
    using expander = int[];
    (void) expander{ 0, (std::cout << typeid(TS).name() << '\n', 0)... };
}

Or, C++17-style:

template <typename ...TS>
void print_type_name(void) {
    (std::cout << ... << (typeid(TS).name() + "\n"s));
}

Demo.

Upvotes: 4

swang
swang

Reputation: 5249

I think you can do this:

void print_type_name()
{
    std::cout<<"\n";
}

template <typename T>
void print_type_name(const T& t)
{
    std::cout<<t<<" : of type "<<typeid(t).name()<<"\n";
}

template <typename T1, typename... Ts>
void print_type_name(const T1& t1, const Ts&... ts)
{
    // Head
    std::cout<<t1<<" : of type "<<typeid(t1).name()<<", ";
    // Tail
    print_type_name(ts...);
}

Whether it's clearer or not I don't know.

Upvotes: 2

Related Questions