Modifying types of tuple elements

533 views Asked by At

Having a list of types as a variadic template argument, it's pretty easy to perform arbitrary type manipulation on them, to get a tuple of modified types as a result. E.g. to wrap each element with a custom wrapper class, one could do:

template<typename T> class Wrapper {};

template<typename ...Values>
using WrappedValues = std::tuple<Wrapper<Values>...>;

using NewTuple = WrappedValues<int, std::string, char>;
static_assert(std::is_same<NewTuple, std::tuple<Wrapper<int>, Wrapper<std::string>, Wrapper<char>>>::value, "");

How to do the same, when having a specialization of std::tuple as an "input"? E.g. what should be placed instead of "???" to make following code compileable:

template<typename T> class Wrapper {};

template<typename Tuple>
using WrappedTupleElements = ???;

using NewTuple = WrappedTupleElements<std::tuple<int, std::string, char>>;
static_assert(std::is_same<NewTuple, std::tuple<Wrapper<int>, Wrapper<std::string>, Wrapper<char>>>::value, "");

I know about the possibility to access types of tuple elements using std::tuple_element and recursive template instantiation, but I don't know how to gather types created this way into one tuple.

Preferred would be pure C++14 answer, but proposals that use C++17, widely available TSes, or external libraries (e.g. boost) are also welcome.

2

There are 2 answers

1
W.F. On BEST ANSWER

Why not to use additional struct template which allow specialization:

#include <string>
#include <tuple>
#include <type_traits>

template<typename T> class Wrapper {};

template<typename Tuple>
struct WrappedTupleElements;

template <class... Values>
struct WrappedTupleElements<std::tuple<Values...>> {
    using type = std::tuple<Wrapper<Values>...>;
};

int main() {
    using NewTuple = WrappedTupleElements<std::tuple<int, std::string, char>>::type; 
    static_assert(std::is_same<NewTuple, std::tuple<Wrapper<int>, Wrapper<std::string>, Wrapper<char>>>::value, "");
}

[live demo]

3
Richard Hodges On

A common idiom (e.g. boost mpl) is to employ the concept of metafunctions.

A metafunction is a template class which declares a type called result which yields the result type of applying the metafunction on the inputs.

#include <string>
#include <tuple>
#include <type_traits>

template<typename T> class Wrapper {};

namespace metafunction
{
    template<class MetaFunction> using result_of = typename MetaFunction::result;

    // meta-function which yields Wrapper<Element> from Element
    // type: unary metafunction
    // arg1 = the type to wrap
    // returns Wrapper<arg1>
    //
    template<class Element>
    struct apply_wrapper
    {
        using result = Wrapper<Element>;
    };

    template<class Tuple, template<class> class Function>
    struct transform_elements;

    // meta-function which takes a tuple and a unary metafunction
    // and yields a tuple of the result of applying the metafunction
    // to each element_type of the tuple.
    // type: binary metafunction
    // arg1 = the tuple of types to be wrapped
    // arg2 = the unary metafunction to apply to each element_type
    // returns tuple<result_of<arg2<element>>...> for each element in arg1

    template<class...Elements, template<class> class UnaryMetaFunction>
    struct transform_elements<std::tuple<Elements...>, UnaryMetaFunction>
    {
        template<class Arg> using function = UnaryMetaFunction<Arg>;
        using result = std::tuple
        <
            result_of<function<Elements>>...
        >;
    };
}

int main() {
    using input_type = std::tuple<int, std::string, char>;

    using namespace metafunction;

    using wrapped_tuple = result_of<transform_elements<input_type, apply_wrapper>>;

    static_assert(std::is_same<wrapped_tuple, std::tuple<Wrapper<int>, Wrapper<std::string>, Wrapper<char>>>::value, "");
}