[C++]ふと文字列リテラルによる名前付きtupleが欲しくなった話。

何らかのクラスをタグとして与えるtuple、なら作る方法はネット上にいくらでも転がっているが、タグ用のクラスをいちいち宣言しなければならないのが鬱陶しいし、名前空間を汚してしまうのも気になるので、文字列リテラルで与えられるようにできないかなぁとちょっと思った結果がこれである。C++20では非型テンプレートパラメータに(色々と制限はあるものの)クラス型を指定できるようになったので、実現可能になった。
いやあんまり用途はないんだけど、非常に限定的な状況下ではあってもいいような気がして。特に最近はtupleが入れ子になったような、要素が非常に多くなってしまう面倒なものを作っていることもあり、いくつものインデックスで与えるのがちょっと野暮ったく感じていたのだ。

#include <tuple>
#include <iostream>
#include <limits>
#include <cassert>

namespace nt
{

template <size_t N>
struct char_array
{
    constexpr char_array(const char(&s)[N])
    {
        for (size_t i = 0; i < N; ++i) name[i] = s[i];
    }
    template <size_t M>
    constexpr bool operator==(const char_array<M>& that) const
    {
        if constexpr (N != M) return false;
        else
        {
            for (size_t i = 0; i < N; ++i) if (name[i] != that.name[i]) return false;
            return true;
        }
    }
    static constexpr size_t size() { return N; }
    char name[N];
};

template <char_array N, class T> struct name_type { static constexpr char_array name = N; using type = T; };

template <class ...T>
class named_tuple : public std::tuple<typename T::type...>
{
    static constexpr std::tuple<char_array<T::name.size()>...> name = { T::name... };
public:
    template <size_t N, size_t I = 0>
    static constexpr size_t find_name(char_array<N> n)
    {
        if constexpr (I < sizeof...(T))
        {
            if (std::get<I>(name) == n) return I;
            return find_name<N, I + 1>(n);
        }
        else return std::numeric_limits<size_t>::max();
    }
    using std::tuple<typename T::type...>::tuple;
};

template <char_array Name, class ...T>
decltype(auto) get(named_tuple<T...>& t) { return std::get<named_tuple<T...>::find_name(Name)>(t); }
template <char_array Name, class ...T>
decltype(auto) get(const named_tuple<T...>& t) { return std::get<named_tuple<T...>::find_name(Name)>(t); }
template <char_array Name, class ...T>
decltype(auto) get(named_tuple<T...>&& t) { return std::get<named_tuple<T...>::find_name(Name)>(std::move(t)); }

}

int main()
{
    nt::named_tuple<nt::name_type<"i32", int>, nt::name_type<"f32", float>, nt::name_type<"string", std::string>> nt(1, 2.3f, "4.5");
    std::cout << get<"i32">(nt)<< std::endl;//1
    assert(get<"i32">(nt) == std::get<0>(nt));
    std::cout << get<"f32">(nt)<< std::endl;//2.3
    assert(get<"f32">(nt) == std::get<1>(nt));
    std::cout << get<"string">(nt) << std::endl;//4.5
    assert(get<"string">(nt) == std::get<2>(nt));
}