Lauren S
Lauren S

Reputation: 319

Parameter Pack Sorting (Or Equivalent Behavior)

I want:

template<class ... T>
auto foo()
{
  // ✨ magic ✨
}

Such that:

(foo<int, char, bool>() == foo<char, int, bool>()) // True
(foo<int>() == foo<char>()) // False

In other words, I want foo to return a unique id for the combination of types passed to it rather than the permutation of types passed to it.

My first idea was that there might be some way to sort the parameter pack at compile time, though I'm not sure how exactly that would work.

My current solution is this:

// Precondition: Client must pass the parameters in alphabetical order to ensure the same result each time
template<class ... T>
std::type_index foo()
{
  return std::make_type_index(typeid(std::tuple<T ... >));
}

The problem with this is that it doesn't work if the client is using type aliases. For example:

using my_type = char;
(foo<bool, int, my_type>() == foo<bool, char, int>()) // False

One idea I had is to assign a new prime number as an id for every new type that the function handles. Then I could assign a unique id for a particular combination of types by multiplying their prime number ids together. For example:

id of int = 2
id of bool = 3
id of char = 5
id of <int, bool, char> = 2 * 3 * 5 = 30
id of <bool, int, char> = 3 * 2 * 5 = 30

Only problem is how I'm going to assign unique prime number ids to each type without incurring a runtime penalty.

Upvotes: 1

Views: 449

Answers (3)

康桓瑋
康桓瑋

Reputation: 42916

You can use the gcc /clang's extension __PRETTY_FUNCTION__ (__FUNCSIG__ in msvc) as the identifier of each type and save them into std::array, then sort it to get the unique combination of type list.

But since std::arrays of different sizes cannot be compared with each other, we also need create a new type (unique_id) to wrap the sorted array and overload its operator==:

#include <algorithm>
#include <array>
#include <string_view>

template <class T>
constexpr std::string_view id() {
#ifdef __GNUC__
  return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
  return __FUNCSIG__;
#endif
}

template<std::size_t N>
class unique_id {
  std::array<std::string_view, N> value;
 public:
  constexpr unique_id(const std::array<std::string_view, N>& value) : value(value) { }
  template<std::size_t M>
  constexpr bool operator==(const unique_id<M>& other) const {
    if constexpr (N == M) return value == other.value;
    else return false;
  }
};

template<class... Ts>
constexpr auto foo() {
  std::array<std::string_view, sizeof...(Ts)> names{id<Ts>()...};
  std::ranges::sort(names);
  return unique_id(names);
}

static_assert(foo<int, char, bool>() == foo<char, int, bool>());
static_assert(foo<int>() != foo<char>());

Demo.

Upvotes: 2

Barry
Barry

Reputation: 303397

With Boost.Mp11:

template <typename T>
constexpr std::string_view type_name() {
    return __PRETTY_FUNCTION__; // close enough
}

template <typename T, typename U>
using type_less = mp_bool<type_name<T>() < type_name<U>()>;

template <typename... Ts>
constexpr auto foo() {
    return type_name<mp_sort<mp_list<Ts...>, type_less>>();
}

static_assert(foo<int, char>() == foo<char, int>());

Upvotes: 2

yuri kilochek
yuri kilochek

Reputation: 13484

Using Boost.Hana:

#include <boost/hana/set.hpp>
#include <boost/hana/type.hpp>

template<typename... T>
constexpr auto foo() {
    return boost::hana::make_set(boost::hana::type_c<T>...);
}

using my_type = char;
static_assert(foo<bool, int, my_type>() == foo<bool, char, int>());

Upvotes: 5

Related Questions