kfmfe04
kfmfe04

Reputation: 15327

How do I strip a tuple<> back into a variadic template list of types?

Is there a way to strip a std::tuple<T...> in order to get it back to T...?

Example

Suppose vct<T...> is a pre-existing variadic class template,

using U = std::tuple<int,char,std::string>;
using X = vct<int,char,std::string>;
using Y = vct< strip<U> >;            // should be same as X

Notes

I know about std::tuple_element, but I need all the elements, in a form that is usable as T...

For reference, I have found this question, which is similar, but my needs are somewhat simpler (so I hope there is a simpler solution): all I need is the list of types that are in the tuple - I don't care about the actual values of a tuple instance.

Upvotes: 20

Views: 8114

Answers (5)

Daniel Frey
Daniel Frey

Reputation: 56863

You can not directly "return" a parameter pack, so what you need is something like this:

template< typename... Ts >
struct vct
{ ... };

template< typename >
struct make_vct;

template< typename... Ts >
struct make_vct< std::tuple< Ts... > >
{
    typedef vct< Ts... > type;
};

and use

using Y = make_vct< U >::type;

Upvotes: 5

scx
scx

Reputation: 3937

Using c++17 std::apply.

using U = std::tuple<int, char, std::string>;
using X = vct<int, char, std::string>;
using Y = vct<strip<U>>; // should be same as X

auto my_y = std::apply(
        [](auto... ts) {
            using Y = vct<decltype(ts)...>;
            return Y{};
        },
        U{});

Upvotes: 2

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

The goal here is to be able to copy a parameter pack from an instance of a template, to another template. I didn't restrict it to tuple, because... why restrict it to tuple?

template<template<typename...>class Target, typename Src>
struct copy_pack_types;

template<template<typename...>class Target, template<typename...>class Src, typename... Ts>
struct copy_pack_types< Target, Src<Ts...> > {
  typedef Target<Ts...> type;
};

template<template<typename... Ts>class Target, typename Src>
using CopyPackTypes = typename copy_pack_types<Target, Src>::type;

#include <string>
#include <tuple>
template<typename... Ts> struct vct;
template<typename... Ts> struct vct2;
using U = std::tuple<int,char,std::string>;
using X = vct<int,char,std::string>;
using Y = CopyPackTypes< vct, U >;            // should be same as X
using Z = CopyPackTypes< vct2, U >;            // should be different from X

#include <iostream>
#include <type_traits>
int main() {
  std::cout << std::is_same< X, Y >::value << "\n";
  std::cout << std::is_same< Z, Y >::value << "\n";
  std::cout << std::is_same< Z, vct2<int,char,std::string> >::value << "\n";
}

output is "1 0 1" as expected.

The CopyPackTypes takes a target template, and a source type that was constructed from a parameter pack as its only argument. It then copies the parameter pack to the target template.

One standard technique is to carry parameter packs around is to create an otherwise no use type like:

template<typename...>
struct types {};

which only exists as a placeholder for a list of types. You can then pass a few of these to another template, and each pack doesn't "step on" each other. When you need to apply it to a target template, you use something like the above "CopyPackTypes" to apply it.

Similar techniques are used for packs of indexes:

template<size_t...>
struct seq {};

otherwise useless types that are "black slates" to carry clumps of parameters around.

Upvotes: 3

Andy Prowl
Andy Prowl

Reputation: 126432

No, this is not possible. Argument packs are the result of type deduction, and they can't be produced in other contexts.

You could do something similar to what you're asking for this way:

template<template<typename...> class T, typename>
struct instantiate_with_arg_pack { };

template<template<typename...> class T, typename... Ts>
struct instantiate_with_arg_pack<T, std::tuple<Ts...>>
{
    using type = T<Ts...>;
};

template<typename... Ts>
struct vct { };

int main()
{
    using U = std::tuple<int,char,std::string>;
    using X = vct<int,char,std::string>;
    using Y = instantiate_with_arg_pack<vct, U>::type;
}

Actually, you don't need to hold the argument pack in a tuple: any variadic class template is OK:

template<template<typename...> class T, typename>
struct instantiate_with_arg_pack { };

template<
    template<typename...> class T, 
    template<typename...> class U, // <===
    typename... Ts
    >
struct instantiate_with_arg_pack<T, U<Ts...>>
//                                   ^^^^^^^^
{
    using type = T<Ts...>;
};

template<typename... Ts>
struct vct { };

int main()
{
    using U = std::tuple<int,char,std::string>;
    using X = vct<int,char,std::string>;
    using Y = instantiate_with_arg_pack<vct, X>::type;
    //                                        ^

    // Won't fire
    static_assert(
        std::is_same<Y, vct<int,char,std::string>>::value, 
        "Error!");
}

And here is a live example.

Upvotes: 14

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361332

template<typename>
struct strip;

template<typename ...T>
struct strip<std::tuple<T...>>
{
   using type = vct<T...>;
};

then use this as:

using Y = strip<U>::type;

Now Y is same as X.

Upvotes: 23

Related Questions