简体   繁体   中英

Represent a set of types using C++ template meta-programming

How to write a template meta_set<class...Args> such that meta_set::type` is same for all permutations of Ts?

In other words, we want to have the same meta_set<>::type whenever the list of arguments is the same regardless of the order, that is, when viewed as a set (or multiset if it's easier).

For example,

std::is_same< meta_set<int,double,string>::type, meta_set<double,string,int>::type  >::value == true
std::is_same< meta_set<int,double,string>::type, meta_set<double,string,bool>::type  >::value == false

This may come handy in situations when you want to have a single instantiation of a template per a set of template parameters.

NOTE: this is not a homework assignment, but something I got curious about when working on a template heavy code at work. I'm not a meta-programming expert, so I thought maybe people can share their knowledge.

There is no way to globally order all types at compile time; access to things like typeid(T).before(typeid(U)) is not constexpr . So you cannot make two arbitrary meta_set<A,B> and meta_set<B,A> be the same type, as you cannot sort.

There is no way to modify the behavior of std::is_same to return true if the types that are not the same. Any code that attempted to do so (say via specializing std::is_same ) would violate the requirements of std::is_same , which would make the program ill-formed, no diagnostic required.

If you restricted your set of types to some subset of all types, you can do this. The easiest way would be to have a centralized list:

template<class...T>
struct types_t {};

using global_order = types_t<int, double, std::string>;

then, with some template metaprogramming, you can get the index of types in the global_order , and then write a type-sorter based on this.

Then

template<class Types>
struct type_sorter;

template<class...Ts>
struct type_sorter<types_t<Ts...>> {
  // todo
  using type=types_t< result_somehow >;
};

Once that is written:

template<class...Ts>
using meta_set = typename type_sorter< types_t<Ts...> >::type;

would work.

There are probably solutions on how to sort stuff at compile time with templates on stack overflow. Personally I find merge sort easiest to write in template metaprogramming of all the n log(n) sorts. Last time it did it it took about 100-odd lines of dense template code? Including writing a TMP library. However, TMP libraries now exist, and may even have type-sorting code pre-written for you.

Now, a single global ordering is the easiest thing to do. We could make it a bit more powerful by teaching the type sorter about templates, ordering templates, and then ordering said template instances by their constituant types and values, etc.

That gets hard, and it requires work for each and every template, kind of template (so template<class...>class vs template<class, std::size_t>class ) and base type ( int , float , struct foo ) supported.

It would be a lot of work.

Slightly easier would be to write a custom trait

template<class Lhs, class Rhs>
struct smart_is_same;

that would act like std::is_same , except when fed meta_set s would check if they have the same contents instead of looking for strict type equality. However, your comments mention that this isn't your actual problem, but rather you are talking about passing the meta_set arguments to a different template and wants them in a canonical order.

The only conceivable way to canonically represent a set of types is with a sorted list (or something that can be converted to a sorted list, eg a binary search tree). But there's no natural compile-time ordering between types in C++, so there's nothing to sort by .

Should C++ require std::type_info::before to be constexpr , or define a template with similar functionality, say std::is_before<typename A, typename B> , it would create a global static ordering between types which would make creating canonically ordered lists possible. Sadly this is not the case.

Lacking compiler-supported ordering, the programmer must define their own is_before<A,B> for every possible pair of types, which is of course not possible for every pair of types, but only for some finite known-in-advance set.

My idea is not to sort the types, but just to check that a type in first meta set appears in the second.

template <typename... Args>
struct TypeList;

template <typename Head, typename... Tail>
struct TypeList<Head, Tail...>
{
    using TailList = TypeList<Tail...>;
    using HeadType = Head;

    static_assert(!TailList::template contains<Head>(), "Types must be unique");

    static constexpr int size()
    {
        return 1 + TailList::size();
    }

    template <typename Type>
    static constexpr bool contains()
    {
        return std::is_same<Head, Type>::value || TailList::template contains<Type>();
    }
};

template<>
struct TypeList<>
{
    static constexpr int size()
    {
        return 0;
    }

    template <typename Type>
    static constexpr bool contains()
    {
        return false;
    }
};


template <typename ListLhs, typename ListRhs>
struct IsSame
{
    static constexpr bool value()
    {
        return ListLhs::size() == ListRhs::size() && valueImpl();
    }

    static constexpr bool valueImpl()
    {
        return ListLhs::template contains<typename ListRhs::HeadType>() &&
               IsSame<ListLhs,typename ListRhs::TailList>::valueImpl();
    }
};

template <typename ListLhs>
struct IsSame<ListLhs, TypeList<>>
{
    static constexpr bool value()
    {
        return false;
    }

    static constexpr bool valueImpl()
    {
        return true;
    }
};

template <>
struct IsSame<TypeList<>, TypeList<>>
{
    static constexpr bool value()
    {
        return true;
    }

    static constexpr bool valueImpl()
    {
        return false;
    }
};

struct MyStruct{};

using Types = TypeList<int, bool, char, MyStruct, double>;
using TypesSame = TypeList<int, MyStruct, bool, char, double>;
using LessTypes = TypeList<int, bool, char>;
using EmptyTypes = TypeList<>;

static_assert(IsSame<Types, TypesSame>::value(), "Underlying types should be the same");
static_assert(!IsSame<Types, LessTypes>::value(), "Less types");
static_assert(!IsSame<Types, EmptyTypes>::value(), "Not the same as Empty");
static_assert(IsSame<EmptyTypes, EmptyTypes>::value(), "Empty types");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM