1

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.

  • 2
    so, what's your question? – Tomaz Canabrava May 09 '17 at 13:08
  • 1
    Pro tip: Post the website/email address where to upload the solution so you can save yourself some work. – nwp May 09 '17 at 13:08
  • 5
    I'm voting to close this question as off-topic because StackOverflow will not do your homework for you. – Federico klez Culloca May 09 '17 at 13:10
  • 1
    without more requirements the task is pointless, simply use a `typedef int type;` then all permutations will have the same `type` – 463035818_is_not_an_ai May 09 '17 at 13:15
  • 1
    @FedericoklezCulloca, why? this is not a homework question. – Narek Saribekyan May 09 '17 at 13:16
  • @NarekSaribekyan, sorry, but it had all the smells of a homework question. – Federico klez Culloca May 09 '17 at 13:18
  • 1
    I believe you mean "for all permutations of *the same* Ts" and "but it is different for permutations of different Ts". I think the name "set" is enough of a hint, but it's better if you describe the requirements properly (it helps to get answers that actually suit you). You cannot complain that your AI turned the entire solar system into a paperclip factory after you told it to maximize paperclip production. – R. Martinho Fernandes May 09 '17 at 13:18
  • 3
    I'd close this due to "lack of effort", homework or not. It's also a bit underspecified (is `meta_set`? ) – MSalters May 09 '17 at 13:19
  • 1
    @nwp I see no need for `::type`? We don't have to write C++11 style `meta_set`. – Yakk - Adam Nevraumont May 09 '17 at 13:24
  • This is a legitimate question. I don't quite understand the downvotes. Voting to reopen. OP has a test in the post, they just don't know the appropriate templatese for checking. – AndyG May 09 '17 at 13:24
  • @Yakk Right, one could use a `using` declaration. I didn't think of that. – nwp May 09 '17 at 13:28
  • 1
    Once repopened, the answer is going to be "nope, no can do", because there is no compile-time canonical ordering on types in C++. I mean, I can make the OP's example compile; but it won't solve the problem in general. I could create a system where you can stitch type orderings together, or one where there is a central type order. – Yakk - Adam Nevraumont May 09 '17 at 13:29
  • @Yakk fwiw, there's no need for a *canonical* ordering. Any ordering would do. There isn't any, though, so, yeah, still "nope". – R. Martinho Fernandes May 09 '17 at 13:31
  • @R.MartinhoFernandes `template struct some_order {};` is a non-canonical ordering. It is a manual one; it is *some* ordering. It almost certainly isn't what the OP wants; who wants to have to type out every type in order? It would take a while. – Yakk - Adam Nevraumont May 09 '17 at 13:32
  • @Yakk I'm confused. How is that an ordering? But regardless; what you need is an order that is total, i.e. it orders *all* types. It doesn't have to be canonical. – R. Martinho Fernandes May 09 '17 at 13:34
  • @R.MartinhoFernandes No, it need only order the types used. In fact, you could go as far as to state that it need only order all but one of the types used in each list (and unordered types get shoved in the rear, with an assert that there is at most one). – Yakk - Adam Nevraumont May 09 '17 at 14:13

3 Answers3

5

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_sets 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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

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, e.g. 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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • It doesn't have to be finite, it can be enumerable. For example, I could teach it base types, a few template patterns, and ordering on templates themselves. From this, I could get an infinte number of types ordered, including every `tuple` (on the base types), from a finite list of base generators. Next, I can test if those base checks have failed, and try to use structured binding type bindings to order remaining types (which isn't SFINAE friendly, but better than nothing). Not easy, but solves an infinite subset of the infinite problem. I wouldn't advise it either. But possible. – Yakk - Adam Nevraumont May 09 '17 at 14:27
0

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");
klimov
  • 7
  • 2