2

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?

joe
  • 617
  • 7
  • 22
  • possible duplicate of [What does this variadic template code do?](http://stackoverflow.com/questions/28110699/what-does-this-variadic-template-code-do) – Pradhan Feb 16 '15 at 17:58
  • 1
    can be done inside `print_type_name` with something like: `int dummy[] = {0, (real_print_type_name(), 0)...};` – Jarod42 Feb 16 '15 at 17:59
  • 1
    @Pradhan: Since the code in that question is a bad idea, and this is asking for **good** code, that's not a duplicate. – MSalters Feb 16 '15 at 18:15
  • @MSalters Agreed. However, the answer to that question addresses those issues and gives a good version solving OPs problem. As a plus, it builds up to the final solution in a very logical sequence. So while the questions aren't duplicates, the answers will be. – Pradhan Feb 16 '15 at 18:41

3 Answers3

5

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

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • And with C++17 around the corner, the ugliness can be [_**folded**_](http://en.cppreference.com/w/cpp/language/fold) into `(std::forward(fs)(), ...);` – bit2shift Feb 14 '17 at 05:01
  • `do_in_order` becomes just `template void do_in_order(Fs&&... fs) { (std::forward(fs)(), ...); }`. There's nothing to noop with `(void)`. – bit2shift Feb 14 '17 at 15:02
  • @bit2shift Whatever `fs()` returns could have `operator,` overloaded. Are you claiming that fold expressions on `,` never invoke `operator,`? If so, do you have a link to the paper? Otherwise, in generic code you need to `( void(std::forward(fs)()), ...);` to `void` the return value of `fs()` and make `operator,` safe. `struct evil_comma { template int operator,( T&& t ) const { std::cout << "I am evil" << std::endl; exit(-1); } };`, as an example. – Yakk - Adam Nevraumont Feb 14 '17 at 15:26
  • I'm not saying you should overload `operator,`, but you shouldn't overload `operator,`. Rumour has it that [the overloaded `,` can now guarantee sequencing in C++17](http://en.cppreference.com/w/cpp/language/operator_other#Built-in_comma_operator). – bit2shift Feb 14 '17 at 15:34
  • @bit2shift I am saying generic code cannot assume `operator,` is **not** overloaded. Your code seems to assume this. I do not know if this is because you are screwing up, or if you know something I do not, which is why I am asking. – Yakk - Adam Nevraumont Feb 14 '17 at 15:58
  • Here's the catch: if the overloaded `operator,`'s argument is a value/rvalue reference, `do_in_order(func_a, func_b, func_c, func_d);` will evaluate the functions in the order (D, C, B, A). But if it's an lvalue reference, the order will be (A, B, C, D). [Try playing with the references in the overloaded operator](http://coliru.stacked-crooked.com/a/4f92d3941c9a3eec). – bit2shift Feb 14 '17 at 16:28
  • The gist of it is: either pay attention to the operator's signature or play it safe with the `void()` cast. – bit2shift Feb 14 '17 at 16:39
4
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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • C++17?? Never heard of it. I haven't even gotten very familiar with C++11 yet, let alone C++14 :( – Remy Lebeau Feb 16 '15 at 19:06
  • @RemyLebeau The dark voodoo `(std::cout << ... << (typeid(TS).name() + "\n"s));` is a [*fold-expression*](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4191.html) recently incorporated into the [C++17 Working Paper](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf). T.C. traveled back in time to post this answer assuring us that (1) C++17 will be completed on schedule and (2) the feature will still be in the standard in 2017. (Although I would personally eschew the string concatenation and go with `((std::cout << typeid(TS).name() << '\n'), ...);` instead.) – Casey Feb 16 '15 at 19:21
  • @Casey I'd say (2) is highly likely especially as the Concepts TS also depends on it. I like to be optimistic about (1) now that we did get C++14 finished on schedule :P (Also, N4191's wording is backwards in some places; you'd want the [final revision](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html).) And I agree, not concatenating is better, I just wanted to show off the binary fold ;) – T.C. Feb 16 '15 at 19:24
  • I'll use your first example. I'd venture to say it's not self-explanatory but it's the simplest and it works when you only have types. – joe Feb 18 '15 at 14:25
2

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.

MSalters
  • 173,980
  • 10
  • 155
  • 350
swang
  • 5,157
  • 5
  • 33
  • 55
  • 1
    Obvious typo removed - you had `std::cout` twice for the tail. But you may want to consider if type deduction will work as intended here: passing an `int&` will deduce `int` instead. – MSalters Feb 16 '15 at 18:17
  • @MSalters: indeed, will decltype help here? – swang Feb 16 '15 at 18:25
  • @MSalters `typeid` strips reference-ness and cv-qualification anyway. – T.C. Feb 16 '15 at 19:00
  • According to Scott Meyers (effective modern c++ item 4) the result of typeid(t).name() is implementation dependent – swang Feb 16 '15 at 19:07
  • `typeid(cv T&)` and `typeid(cv T)` are both specified to be the same as `typeid(T)`. What string `.name()` produces is implementation-dependent. – T.C. Feb 16 '15 at 19:09
  • It's important that each templated version of the function work using only the types, i.e. no instances are passed to the function. The usage of `typeid` to print type names was purely for example (and irrelevant). – joe Feb 16 '15 at 21:40
  • That's ok, it can still work, the above doesn't need to invoke on instance, you can peel off templates just based on types unpacking – swang Feb 16 '15 at 22:23
  • Then `print_type_name()` becomes ambiguous (`T=int`, or `T1=int, Ts=<>`). – joe Feb 18 '15 at 14:20