Я хотел бы показать вам тип, который я создал, экспериментируя с буквальными, не типовыми параметрами шаблона в C ++ 20.
Идея состоит в том, чтобы предоставить тип, который объединяет кортежоподобные типы (std :: tuple, std :: pair, даже std :: variant) и предоставляет доступ к своим полям с помощью строкового литерала. Кроме того, он должен учитывать непротиворечивость базовых типов.
Предполагаемое использование следующее:
constexpr auto foo = with_names<std::tuple, named<int, "ciao">, named<float, "hola">>{ 1, 2.0f };
constexpr auto bar = with_names<std::variant, named<int, "ciao">, named<float, "hola">>{ 2.0f };
constexpr auto hola = get<"hola">(foo);
constexpr auto ciao = get<"ciao">(bar); // Fails because "ciao" is not the active member.
constexpr auto var_hola = get<"hola">(bar); // Succeeds.
После битвы с несколькими компилятор ошибки, вот решение, которое я придумал:
#include <algorithm>
// First and foremost, the literal type, that will capture the name string.
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
// This is the type that associates the underlying value type with a given name
template <typename T, StringLiteral Name>
struct named;
// A trait to detect whether all types in a pack are unique.
// The algorithm's complexity is O(n²), therefore this is one of the parts that could certainly be improved.
template <typename T, typename... Ts>
struct are_distinct: std::conjunction<
std::negation<std::is_same<T, Ts>>...,
are_distinct<Ts...>
>{};
template <typename T>
struct are_distinct<T>: std::true_type{};
// Introducing the with_names<Seq, ...> type
template <template <typename...> typename Seq, typename ...Ts>
struct with_names;
// And its related trait
template <typename T>
struct is_with_names: std::false_type{};
template <template <typename...> typename Seq, typename ...Ts, StringLiteral... Names>
struct is_with_names<with_names<Seq, named<Ts, Names>...>>: std::true_type{};
// The real meat
template <template <typename...> typename Seq, typename ...Ts, StringLiteral... Names>
class with_names<Seq, named<Ts, Names>...>: public Seq<Ts...>
{
// This type is only used to trigger SFINAE within the below functions, so to ensure WithNames is the correct type.
// Couldn't directly deal with with_names<> because then perfect forwarding wouldn't be possible (or would it?).
struct sfinae{};
// I would have liked this to be a templated lambda within the get() friend function below, alas
// MSVC would ICE out about it (see: https://godbolt.org/z/fW4q6Es8h )
template <StringLiteral Name, typename WithNames, size_t... Is>
friend constexpr auto impl(WithNames &&wn, const std::index_sequence<Is...> &, std::enable_if_t<is_with_names<std::decay_t<WithNames>>::value, sfinae> = {}) {
auto constexpr idx = ((std::is_same_v<named<void, Names>, named<void, Name>>*(Is+1)) + ...);
if constexpr (idx > 0)
return get<idx-1>(std::forward<WithNames>(wn));
else
static_assert(idx > 0, "Name not found");
}
public:
static_assert((are_distinct<named<void, Names>>::value && ...), "Names must be unique");
using Seq<Ts...>::Seq;
template <StringLiteral Name, typename WithNames>
friend constexpr auto get(WithNames &&wn, std::enable_if_t<is_with_names<std::decay_t<WithNames>>::value, sfinae> = {}) {
return impl<Name>(std::forward<WithNames>(wn), std::index_sequence_for<Ts...>());
}
};
Посмотрите, как он работает на Godbolt: https://godbolt.org/z/4Ecos35e4
Как упоминалось в самом коде, are_distinct<>
сложность алгоритма O (n²), но boost: mpl :: unique показывает, что это могло быть линейно.
Что еще можно улучшить? Вы когда-нибудь использовали бы такой тип?