Charlie
Charlie

Reputation: 115

C++ using a template parameter pack to invoke multiple templated functions

Dear internet people,

I'm currently writing a variant class in C++14 and needed to call a template function for each element in the parameter pack. After some searching I came across this page which features the following sample.

template<typename... Ts> void func(Ts... args){
    const int size = sizeof...(args) + 2;
    int res[size] = {1,args...,2};
    // since initializer lists guarantee sequencing, this can be used to
    // call a function on each element of a pack, in order:
    int dummy[sizeof...(Ts)] = { (std::cout << args, 0)... };
}

I created this small example which demonstrates what I'm trying to achieve.

#include <iostream>

template<typename Ta, typename Tb> struct TypeCmp
{
    static constexpr bool Value = false;
};

template<typename T> struct TypeCmp<T, T>
{
    static constexpr bool Value = true;
};

template<typename T, typename... Ts> struct TypeIdCounter;

template<typename T> struct TypeIdCounter<T>
{
    static constexpr size_t Value = 1;
};

template<typename T, typename Tcur, typename... Ts> struct TypeIdCounter<T, Tcur, Ts...>
{
    static constexpr size_t Value = sizeof(Tcur)+(TypeCmp<T, Tcur>::Value ? 0 : TypeIdCounter<T, Ts...>::Value);
};

template<typename... Ts> struct TypeHolder
{
    template<typename T> struct Info
    {
        static constexpr size_t Id = TypeIdCounter<T, Ts...>::Value;
        static constexpr size_t Size = sizeof(T);

        static void Print(size_t id)
        {
            if (Id == id)
            {
                std::cout << "Type::Id = " << Id << std::endl;
                std::cout << "Type::Size = " << Size << std::endl;
            }
        }
    };

    template<typename T> TypeHolder(const T& value) : id(Info<T>::Id)
    {
        /* copy value to container */
    }

    void Print() const
    {
        int dummy[] = {(Info<Ts>::Print(id), 0)...};
        if (dummy[0])
        {
            /* dummy test needed! */
        }
    }

    size_t id;
};

struct Foo
{
    std::string name;
    int age;
};

typedef TypeHolder<int, long long, bool, Foo> MyTypes;

int main(int argc, char* args[])
{
    std::cout << "Id(int): " << MyTypes::Info<int>::Id << std::endl;
    std::cout << "Id(bool): " << MyTypes::Info<bool>::Id << std::endl;
    std::cout << "Id(Foo): " << MyTypes::Info<Foo>::Id << std::endl;

    MyTypes types(true);

    types.Print();

    return 0;
}

The program listed above generates the following output.

Id(int): 4
Id(bool): 13
Id(Foo): 53
Type::Id = 13
Type::Size = 1

As always I compile my code with the -Wall -Werror flags. So the if (dummy[0])... condition is needed or else I get a unused variable 'dummy' error.

I'm totally clueless why int dummy[] = {(Info<Ts>::Print(id), 0)...}; seems to work since Info::Print is a void method. Could someone enlighten me and explain why this works? And is there a way to prevent the int dummy[] = {(...)}; if (dummy[0]); trick without dropping -Wall -Werror?

I have been trying to search for a explanation but since I have no idea what this construction is even called it's not easy to find anything.

Upvotes: 2

Views: 418

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

using dummy=int[];
(void)dummy{0,
  ((void)(Info<Ts>::Print(id)), 0)...
};

will replace

int dummy[] = {(Info<Ts>::Print(id), 0)...};

and should not generate any warnings. It also handles 0-length lists.

The , operator will take void types. It is built-in, and magic.

The use of (void) next to Print above ensures that if the return type of Print is non-void, and that return type overloads operator,(blah, 0), we don't get unexpected behavior.

Myself, on compilers that support it, I write invoke_for_each.

template<class T>struct tag_type{using type=T;};
template<class T>constexpr tag_type<T> tag{};

template<class F, class...Ts>
void invoke_for_each( F&& f, Ts&&...ts ) {
  using dummy=int[];
  (void)dummy{0,
    ((void)(f(std::forward<Ts>(ts)), 0)...
  };
};

and now at point of use:

invoke_for_each(
  [&](auto tag){
    using T=typename decltype(tag)::type;
    Info<T>::Print(id);
  },
  tag<Ts>...
);

where we move the magic array stuff out of view of the person reading the code.

Upvotes: 2

Related Questions