I've got a typelist providing the following interface :
template <typename... Ts>
struct type_list
{
static constexpr size_t length = sizeof...(Ts);
template <typename T>
using push_front = type_list<T, Ts...>;
template <typename T>
using push_back = type_list<Ts..., T>;
// hidden implementation of complex "methods"
template <uint64_t index>
using at;
struct pop_front;
template <typename U>
using concat;
template <uint64_t index>
struct split;
template <uint64_t index, typename T>
using insert;
template <uint64_t index>
using remove;
};
In another piece of code, I have such a typelist TL of types statically inheriting a base class providing such an interface :
template<typename Derived>
struct Expression {
using type1 = typename Derived::_type1;
using type2 = typename Derived::_type2;
};
struct Exp1 : Expression<Exp1> {
template<typename> friend struct Expression;
private:
using _type1 = float;
using _type2 = int;
};
struct Exp2 : Expression<Exp2> {
template<typename> friend struct Expression;
private:
using _type1 = double;
using _type2 = short;
};
I want to make the typelist of nested types from TL, something like :
using TL = type_list<Exp1, Exp2>;
using TL2 = type_list<TL::type1...>; // type_list<float, double>
but I can't expand TL as it's not an unexpanded parameter pack.
I've thought about index_sequence but can't manage to make it work.
The question is seemingly looking for map, also called transform in C++. TL is one list of types, and the desire is to apply some type-level function (extract ::type1) and have another list of types. Writing transform is straightforward:
template <template <typename> typename fn, typename TL>
struct type_list_transform_impl;
template <template <typename> typename fn, typename... Ts>
struct type_list_transform_impl<fn, type_list<Ts...>>
{
using type = type_list<fn<Ts>...>;
};
template <template <typename> typename fn, typename TL>
using type_list_transform = type_list_transform_impl<fn, TL>::type;
And then the type-level function:
template <typename T>
using type1_of = typename T::type1;
And combine the pieces to get TL2:
using TL2 = type_list_transform<type1_of, TL>; // type_list<float, double>
Example: https://godbolt.org/z/b7TMoac5c
I've created a simple template class called tuple_tag which is identical to std::tuple but only acts as a tag.
// tuple_tag
template <typename...> struct tuple_tag {};
// tuple_tag_element
template <size_t I, typename T>
struct tuple_tag_element;
template <size_t I, typename Head, typename... Tail>
struct tuple_tag_element<I, tuple_tag<Head, Tail...>>
: tuple_tag_element<I - 1, tuple_tag<Tail...>> {};
template <typename Head, typename... Tail>
struct tuple_tag_element<0, tuple_tag<Head, Tail...>>
: std::type_identity<Head> {};
// tuple_tag_element_t
template <size_t I, typename T>
using tuple_tag_element_t = tuple_tag_element<I, T>::type;
// tuple_tag_size
template <typename T>
struct tuple_tag_size;
template <typename T> requires (std::is_reference_v<T> || std::is_const_v<T>)
struct tuple_tag_size<T> : tuple_tag_size<std::remove_cvref_t<T>> {};
template <typename... Ts>
struct tuple_tag_size<tuple_tag<Ts...>>
: std::integral_constant<size_t, sizeof...(Ts)> {};
// tuple_tag_size_v
template <typename T>
inline constexpr size_t tuple_tag_size_v = tuple_tag_size<T>::value;
Here:
using new_type_1 = to_tuple_type<tuple_tag<int, double>>::type;
// new_type_1 = std::tuple<int, double>;
using new_type_2 = to_tuple_tag_type<std::tuple<int, double>>::type;
// new_type_2 = tuple_tag<int, double>;
Where to_tuple_type takes a type template parameter tuple_tag<...> which will be converted into type std::tuple<...>, and to_tuple_tag_type takes a type template parameter std::tuple<...> which will be converted into type tuple_tag<...>.
What I am trying to achieve here is to pass all type template parameters from tuple_tag into std::tuple and vice-versa.
This is my prototype for to_tuple_type where it fails:
template <typename TupleTag>
struct to_tuple_type {
using type = std::tuple<...>;
};
Where type alias will be expanded into:
using type = std::tuple<tuple_tag_element_t<Index, TupleTag>...>;
...
using type = std::tuple<
tuple_tag_element_t<0, TupleTag>,
tuple_tag_element_t<1, TupleTag>,
...,
tuple_tag_element_t<N - 1, TupleTag>
>;
Where N is equal to tuple_tag_size_v<TupleTag>.
What I could only think of is to use std::index_sequence but I don't know where do I introduce the pack.
3 steps. First, make a pack
using indexes=std::make_index_sequence<tuple_tag_size<TupleTag>;
then have a helper that expands the pack. I like this one:
template<auto x>
using constant_t=std::integral_constant<decltype(x),x>;
template<auto x>
constexpr constant_t<x> constant={};
template<std::size_t...Is>
constexpr auto all_indexes( std::index_sequence<Is...> ){
return [](auto f){
return f(constant<Is>...);
};
}
now we can
template<class T>
struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag={};
using type=typename decltype(all_indexes(indexes{})([](auto...Is){
return tag<std::tuple<tuple_tag_element_t<Is, TupleTag>...>;
}))::type;
if that has no tpyos.
There is a simple solution that applies partial template specialization:
// to_tuple_type
template <typename Tup>
struct to_tuple_type;
template <typename... Ts>
struct to_tuple_type<tuple_tag<Ts...>> : std::type_identity<std::tuple<Ts...>> {};
// to_tuple_type_t
template <typename Tup>
using to_tuple_type_t = to_tuple_type<Tup>::type;
// to_tuple_tag_type
template <typename Tup>
struct to_tuple_tag_type;
template <typename... Ts>
struct to_tuple_tag_type<std::tuple<Ts...>> : std::type_identity<tuple_tag<Ts...>> {};
// to_tuple_tag_type_t
template <typename Tup>
using to_tuple_tag_type_t = to_tuple_tag_type<Tup>::type;
I wrote some code that compiles well on recent versions of GCC and Clang, but fails on MSVC:
invalid template argument for template parameter 'TL', expected a class template
Can anyone please explain is this a bug or have I misunderstood something?
Without partial specialization of types_list it works fine on MSVC too.
#include <cstdint>
#include <type_traits>
namespace detail
{
template <std::size_t Index, typename ...Ts>
struct get
{
static_assert(Index < sizeof...(Ts), "types_list::get index out of bounds");
private:
template <std::size_t CurrentIndex, typename ...Us>
struct helper
{
using type = void;
};
template <std::size_t CurrentIndex, typename U, typename ...Us>
struct helper<CurrentIndex, U, Us...>
{
using type = std::conditional_t<CurrentIndex == Index, U, typename helper<CurrentIndex + 1, Us...>::type>;
};
public:
using type = typename helper<0, Ts...>::type;
};
template <template <typename...> typename TL, typename ...Ts>
struct list_impl
{
inline static constexpr std::size_t size = sizeof...(Ts);
template <std::size_t Index>
using get = typename detail::get<Index, Ts...>::type;
};
}
template <typename ...Ts>
struct types_list : public detail::list_impl<types_list, Ts...>
{
};
template <typename T, typename ...Ts>
struct types_list<T, Ts...> : public detail::list_impl<types_list, T, Ts...>
{
private:
using impl = detail::list_impl<types_list, T, Ts...>;
public:
using front = typename impl:: template get<0>;
using back = typename impl:: template get<impl::size - 1>;
};
using t = types_list<int, double>::front;
using t2 = types_list<int, double>::back;
using t3 = types_list<int, char, double>::get<1>;
int main()
{
t x = 10;
t2 y = 1.4;
t3 z = 'a';
}
EDIT: More detailed example https://pastebin.com/snRC0EPi
It seems to be bug indeed. To refer to the current instantiation you can usually omit the template paramter of the current type. This is what MSVC seems to do here. It causes an error because the template expects a template template parameter.
But why you want to use a template template parameter? For CRTP you use the "bound template type".
Update
If you need to instantiate the template with new parameters this can be done easily with a helper type:
namespace helper{
template<
typename VariadicType
>
class GetTemplateOfVariadicType{
};
template<
template <typename...> typename Template,
typename... Ts
>
class GetTemplateOfVariadicType<Template<Ts...>>
{
public:
template<typename... T>
using type = Template<T...>;
};
}
Usage:
using OtherTL = typename ::helper::GetTemplateOfVariadicType<TL>::template type<int, bool, char>;
See here: godbolt
Consider this:
template <typename Pack, template <typename...> class = std::tuple> struct foo;
template <template <typename...> class P, typename... Ts, template <typename...> class Q>
struct foo<P<Ts...>, Q> {
using type = Q<P<Ts>...>;
};
I've placed the default template in the typedef to be std::tuple to make this compile, but what I actually want is the default template to be P, and I don't know of any syntax that allows this. I kept the typedef simple to illustrate the point, which is trying to get P as the default template. So I came up with the following workaround that seems kind of ugly:
template <typename Pack, template <typename...> class...> struct foo;
template <template <typename...> class P, typename... Ts, template <typename...> class Q>
struct foo<P<Ts...>, Q> {
using type = Q<P<Ts>...>;
};
template <template <typename...> class P, typename... Ts>
struct foo<P<Ts...>> {
using type = P<P<Ts>...>;
};
Is there any better way to do it than this? Perhaps some c++17 syntax that I'm not aware of?
In this case, using (combined with template) is your friend.
You need a type-traits to extract the container in Pack, by example
template <typename>
struct bar;
template <template <typename...> class P, typename ... Ts>
struct bar<P<Ts...>>
{
template <typename ... Us>
using templ_type = P<Us...>;
};
so you can extract the default value, for the second template parameter, from Pack as follows
template <typename Pack,
template <typename...> class = bar<Pack>::template templ_type>
struct foo;
The following is a full compiling example
#include <type_traits>
template <typename ...>
struct baz
{ };
template <typename>
struct bar;
template <template <typename...> class P, typename ... Ts>
struct bar<P<Ts...>>
{
template <typename ... Us>
using templ_type = P<Us...>;
};
template <typename Pack,
template <typename...> class = bar<Pack>::template templ_type>
struct foo;
template <template <typename...> class P, typename... Ts,
template <typename...> class Q>
struct foo<P<Ts...>, Q>
{ using type = Q<P<Ts>...>; };
int main()
{
foo<baz<short, int, long>> f0;
static_assert( std::is_same<decltype(f0)::type,
baz<baz<short>, baz<int>, baz<long>>>{}, "!" );
}
If you're looking for a different default that's an identity, you can create such a thing:
template <class... I> struct foo_id_impl;
template <template <class...> class P, class... Ts>
struct foo_id_impl<P<Ts>...> { using type = P<P<Ts>...>; };
template <class... Id>
using foo_id = typename foo_id_impl<Id...>::type;
template <class Pack, template <class...> class = foo_id>
struct foo;
I'm not sure that's necessarily better than your solution, but maybe if need this in multiple places.
Consider this fully working code:
#include <type_traits>
template <typename T, typename IndexPack> struct Make;
template <typename T, template <T...> class P, T... Indices>
struct Make<T, P<Indices...>> {
using type = P<(Indices+1)..., (-3*Indices)..., (Indices-1)...>;
};
template <int...> class Pack;
int main() {
static_assert (std::is_same<Make<int, Pack<1,2,3,4>>::type,
Pack<2,3,4,5, -3,-6,-9,-12, 0,1,2,3>>::value, "false");
}
What I actually want the output to be is
Pack<2,-3,0, 3,-6,1, 4,-9,2, 5,-12,3>
instead of Pack<2,3,4,5, -3,-6,-9,-12, 0,1,2,3>. I first tried
using type = P<(Indices+1, -3*Indices, Indices-1)...>;
but that is simply understood by the compiler to be a useless comma operator. What is the desired syntax to get what I want? If there is no such syntax, what is the cleanest way to do this, keeping in mind that using Indices 3 times is just an example (we may want to use it more than 3 times). Please don't tell me that I have to write a helper to extract the individual packs and then "interlace" all the elements. That nightmarish method cannot be the best solution (and such a solution would also only work if we knew exactly how many individual packs to extract).
Would defining
template <typename T, template <T...> class P, T I>
struct Component {
using type = P<I+1, -3*I, I-1>;
};
help somehow? Make a pack expansion on this?
Yes, you can concat recursively:
template <typename, typename, typename> struct Concat;
template <typename T, template <T...> class P, T... A, T... B>
struct Concat<T, P<A...>, P<B...>> {
using type = P<A..., B...>;
};
template <typename T, typename IndexPack> struct Make;
template <typename T, template <T...> class P, T... I, T F >
struct Make<T, P<F, I...>> {
using type = typename Concat<T,
typename Make<T, P<F>>::type,
typename Make<T, P<I...>>::type>::type;
};
template <typename T, template <T...> class P, T I>
struct Make<T, P<I>> {
using type = P<I+1, -3*I, I-1>;
};
Demo
This was inspired by Columbo's solution. It uses the pack expansion syntax that I originally sought, namely
using type = typename Merge<T, typename Component<T, P, Indices>::type...>::type;
As a result, now Make is reusable, first using Triple, and then using Quadruple, so any number of usages of Indices can be expanded simultaneously. Here Component is a template-template-template parameter passed into Make:
#include <type_traits>
template <typename T, typename... Packs> struct Merge;
template <typename T, template <T...> class P1, template <T...> class P2, T... Is, T... Js>
struct Merge<T, P1<Is...>, P2<Js...>> {
using type = P1<Is..., Js...>;
};
template <typename T, typename Pack1, typename Pack2, typename... Packs>
struct Merge<T, Pack1, Pack2, Packs...> {
using type = typename Merge<T, Pack1, typename Merge<T, Pack2, Packs...>::type>::type;
};
template <typename T, template <T...> class P, T I>
struct Triple {
using type = P<I+1, -3*I, I-1>;
};
template <typename T, template <T...> class P, T I>
struct Quadruple {
using type = P<I+1, -3*I, I-1, I>;
};
template <typename T, typename IndexPack,
template <typename U, template <U...> class P, U I> class Component> struct Make;
template <typename T, template <T...> class Z, T... Indices,
template <typename U, template <U...> class P, U I> class Component>
struct Make<T, Z<Indices...>, Component> {
using type = typename Merge<T, typename Component<T, Z, Indices>::type...>::type;
};
template <int...> class Pack;
int main() {
static_assert (std::is_same<Make<int, Pack<1,2,3,4>, Triple>::type,
Pack<2,-3,0, 3,-6,1, 4,-9,2, 5,-12,3>>::value, "false");
static_assert (std::is_same<Make<int, Pack<1,2,3,4>, Quadruple>::type,
Pack<2,-3,0,1, 3,-6,1,2, 4,-9,2,3, 5,-12,3,4>>::value, "false");
}