Реализация «кортеж с именованными полями»

Я хотел бы показать вам тип, который я создал, экспериментируя с буквальными, не типовыми параметрами шаблона в 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 <> показывает, что это могло быть линейно.

Что еще можно улучшить? Вы когда-нибудь использовали бы такой тип?

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *