1
\$\begingroup\$

I'd like to show you a type I made while experimenting with literal, non-type template parameters in C++20.

The idea is to provide a type that wraps up tuple-like types (std::tuple, std::pair, even std::variant) and provides access to its fields using a string literal. Also, it has to respect the constexpr-ness of the underlying types.

The intended usage is the following:

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.

After battling with a few compiler bugs, this is the solution I came up with:

#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...>());
    }
};

See it working on godbolt: https://godbolt.org/z/4Ecos35e4

As mentioned in the code itself, the are_distinct<> algorithm complexity is O(n²), but boost:mpl::unique<> shows it could be linear.

Anything else that could be improved? Would you ever use such a type?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ constexpr StringLiteral(const char (&str)[N]) { is only taking a non-const ref - limiting it's useful-ness - try constexpr StringLiteral(const char (& const str)[N]) { which will happily work with const char[N] and const version. \$\endgroup\$
    – Mr R
    Mar 22 at 12:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.