I've run into an interesting C++20 problem where I want to apply the same templated function to each type listed in a std::tuple
. Here's some pseudocode to illustrate the idea:
template <typename T>
void logType()
{
std::cout << "Type: " << typeid(T).name() << std::endl;
}
int main()
{
using MyTypes = std::tuple<float, int, std::string>;
tapply<MyTypes>([]<typename T>() {
logType<T>();
});
}
So after a few unsuccessful attempts I believe I've found the ugly way to do so in C++20:
template <typename Tuple, typename Func>
void tapply(Func&& func)
{
static constexpr Tuple defaultTuple{};
// Apply the tuple to the callable function
std::apply ([&func](auto&&... args) {
(func.template operator() < std::decay_t<decltype(args)> > (), ...);
}, defaultTuple);
}
And this is how the example below would concretely looks like:
void main()
{
using MyTypes = std::tuple<float, int, std::string>;
tapply<MyTypes>([] <typename T>() {
logType<T> ();
});
}
So far this seems to work like a charm in unit tests but I'm surprised there is no standard way of doing this in the standard library. Somehow this feels like almost the most standard use case where I would use the new templated lambda function available in C++20.
So I've got two questions for the template addicts:
Thanks!
Am I missing an existing method of the standard library that would already exhibit the same feature? It seems that
std::apply
will be able to do this starting from C++23.
I don't think so.
From my reading, std::apply
c++23 won't help more (it allows custom tuples)
Would you recommend a more elegant / standard implementation compatible with C++20?
Not sure which part you consider ugly,
I would do something like:
template <typename Tuple, typename Func>
void tapply(Func&& func)
{
[]<typename...Ts>(std::type_identity<std::tuple<Ts...>>)
{
(func.template operator()<Ts>(), ...);
}(std::type_identity<Tuple>{});
}
which allows non-default constructible types.
If you want to support tuple-like type (as std::pair
/std::array
), std::index_sequence
should replace some part of above code.
To avoid func.template operator()<Ts>
syntax, I tend to have deducible template parameters, as using std::type_identity
for types, and std::integral_constant
for non-type template parameters.
Something like
template <typename Tuple, typename Func>
void tapply(Func&& func)
{
[]<typename...Ts>(std::type_identity<std::tuple<Ts...>>)
{
(func(std::type_identity<Ts>{}), ...);
}(std::type_identity<Tuple>{});
}
with usage
int main()
{
using MyTypes = std::tuple<float, int, std::string>;
tapply<MyTypes>([] <typename T>(std::type_identity<T>)) {
logType<T>();
});
// or
tapply<MyTypes>([](auto type)) {
logType<typename decltype(type)::type>();
});
return 0;
}