Reputation: 115
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
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