Search code examples
c++templatesc++17fold-expression

Print typeids of types in template parameter list


I would like to define a function that prints the typeid names of types in its template parameter list. The function accepts no value parameters. So for example calling print<int>() should print int, print<int, double>() should print int, double and calling print<>() should output the string no types.

I have a solution that works, however it uses an extra vector to do this, suggested by this answer:

#include <iostream>
#include <vector>

template<class... T>
void print() {
    std::vector<const char*> ids{ typeid(T).name()... };

    if (ids.empty()) {
        std::cout << "no types\n";
        return;
    }

    const auto last = ids.size() - 1;

    for (size_t i = 0; i < last; i++)
        std::cout << ids[i] << ", ";

    std::cout << ids[last] << '\n';
}

int main() {
    print<>();
    print<int, double, char>();
    return 0;
}

As you can see, this function isn't very compile-time friendly. I tried to create a version of the code that doesn't involve a vector, however there is a different problem.

#include <iostream>

template<typename... Types>
void print() {
    ((std::cout << typeid(Types).name() << ", "), ...);
}

template<>
void print() {
    std::cout << "no types\n";
}

int main() {
    print<>();
    print<int, double, char>();
    return 0;
}

Output

no types
int, double, char,

This version is printing a comma after the last type in the template parameter list. I would like to get rid of this comma.


Solution

  • To have the comma only appear before the type but not after it you could print the first type, then print everything else with the comma before the type.

    #include <iostream>
    
    template<typename T, typename...Types>
    void print_helper() {
        std::cout << typeid(T).name(); // first type
        ((std::cout << ", " << typeid(Types).name()), ...); // everything else
    }
    
    template<typename... Types>
    void print() {
        print_helper<Types...>();
    }
    
    template<>
    void print() {
        std::cout << "no types\n";
    }
    
    int main() {
        print<>();
        print<int, double, char>();
        return 0;
    }
    
    no types
    i, d, c
    

    Note that only msvc prints int, both clang and gcc use i for int, see typeid.

    If you want the approach with the loop, then you can replace the vector with a std::array which does no heap allocations in the assembly.

    #include <array>
    #include <iostream>
    
    template<class... T>
    void print() {
        std::array<const char*, sizeof...(T)> ids{ typeid(T).name()... };
    
        if (ids.empty()) {
            std::cout << "no types\n";
            return;
        }
    
        const auto last = ids.size() - 1;
    
        for (size_t i = 0; i < last; i++)
            std::cout << ids[i] << ", ";
    
        std::cout << ids[last] << '\n';
    }
    
    int main() {
        print<>();
        print<int, double, char>();
        return 0;
    }