I'm trying to build the compile-time "tupled" version of std::transform
. This type traits should take in a std::tuple
, a unary type trait that "returns" a type (such as std::remove_pointer
) and should produce a std::tuple
whose types are the result of applying the type trait to the corresponding type of the input std::tuple
For example, my_tuple_trasform<std::tuple<int*, float&, double*>, std::remove_pointer>::type
should be equivalent to std::tuple<int, float&, double>
.
I could easily write a solution recursively, but I was trying to implement an "iterative" solution through an expansion pack, and I can't figure out what I'm getting wrong.
Here's my code:
template <typename Tuple, template<typename> typename TraitPredicate>
struct my_tuple_trasform;
template <template<typename> typename TraitPredicate, typename... Ts>
struct my_tuple_trasform<std::tuple<Ts...>, TraitPredicate>
{
using type = std::tuple<(typename TraitPredicate<Ts>::type)...>;
};
The compiler produces an unhelpful error, where it points out that the expression
(typename TraitPredicate<Ts>::type)
is not recognized as a parameter pack and it also seems to think that the whole thing, ellipsis included, is just the first type of the std::tuple
.
I'm trying to build the compile-time "tupled" version of std::transform
. This type traits should take in a std::tuple
, a unary type trait that "returns" a type (such as std::remove_pointer
) and should produce a std::tuple
whose types are the result of applying the type trait to the corresponding type of the input std::tuple
For example, my_tuple_trasform<std::tuple<int*, float&, double*>, std::remove_pointer>::type
should be equivalent to std::tuple<int, float&, double>
.
I could easily write a solution recursively, but I was trying to implement an "iterative" solution through an expansion pack, and I can't figure out what I'm getting wrong.
Here's my code:
template <typename Tuple, template<typename> typename TraitPredicate>
struct my_tuple_trasform;
template <template<typename> typename TraitPredicate, typename... Ts>
struct my_tuple_trasform<std::tuple<Ts...>, TraitPredicate>
{
using type = std::tuple<(typename TraitPredicate<Ts>::type)...>;
};
The compiler produces an unhelpful error, where it points out that the expression
(typename TraitPredicate<Ts>::type)
is not recognized as a parameter pack and it also seems to think that the whole thing, ellipsis included, is just the first type of the std::tuple
.
- 1 please include a minimal reproducible example and the compiler error message in the question – 463035818_is_not_an_ai Commented Mar 28 at 13:22
2 Answers
Reset to default 6It was close: you have to skip the parenthesis in using type = std::tuple<(typename TraitPredicate<Ts>::type)...>;
#include <tuple>
template <typename Tuple, template<typename> typename TraitPredicate>
struct my_tuple_trasform;
template <template<typename> typename TraitPredicate, typename... Ts>
struct my_tuple_trasform<std::tuple<Ts...>, TraitPredicate>
{
using type = std::tuple<typename TraitPredicate<Ts>::type...>;
};
using T1 = std::tuple<int*, float&, double*>;
using T2 = my_tuple_trasform<T1, std::remove_pointer>::type;
static_assert (std::is_same_v<T2, std::tuple<int, float&, double>>);
int main() {}
Demo
The error was caused by wrapping the pack expansion in extra parentheses, which prevented the compiler from recognizing it as a pack expansion and made it treat the expression as a single type. This has been mentioned already in the other answer.
However, here is a slighly less verbose solution using templated lambda (require c++20 or later) with decltype
:
#include <tuple>
#include <type_traits>
template <typename Tuple, template<typename> typename TraitPredicate>
using my_tuple_transform_t = decltype([]<typename... Ts>(std::tuple<Ts...>*)
-> std::tuple<typename TraitPredicate<Ts>::type...> { return {}; }
(static_cast<Tuple*>(nullptr)));
((See live demo))
Or for the case of, older compilers:
// Helper function template that is never defined, exists solely for type deduction.
template <template<typename> class TraitPredicate, typename... Ts>
auto transform_tuple_impl(std::tuple<Ts...>*) -> std::tuple<typename TraitPredicate<Ts>::type...>;
// Type alias that uses the helper function with decltype to deduce the transformed tuple type.
template <typename Tuple, template<typename> class TraitPredicate>
using my_tuple_transform_t = decltype(transform_tuple_impl<TraitPredicate>(static_cast<Tuple*>(nullptr)));
((See live demo))