web-dev-qa-db-ja.com

std :: variantのタイプ別にインデックスを取得する

std::variantで特定のtypeindexを取得するためのユーティリティが標準ライブラリにありますか?それとも自分で作ればいいですか?つまり、std::variant<A, B, C>Bのインデックスを取得し、1を返すようにしたいと考えています。

反対の操作にはstd::variant_alternativeがあります。もちろん、std::variantのリストには多くの同じ型が存在する可能性があるため、この操作は全単射ではありませんが、私にとっては問題ではありません(リストに最初に出現する型、またはstd::variantリストに一意の型が存在する可能性があります)。

13
Bargor

私は this タプルの答えを見つけ、それを少し修正しました:

template<typename VariantType, typename T, std::size_t index = 0>
constexpr std::size_t variant_index() {
    if constexpr (index == std::variant_size_v<VariantType>) {
        return index;
    } else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
        return index;
    } else {
        return variant_index<VariantType, T, index + 1>();
    }
} 

それは私にとってはうまくいきますが、今では構造として、constexprなしで古い方法でそれを行う方法に興味があります。

3
Bargor

index() がほぼ正しいことをすでに行っているという事実を利用できます。

さまざまなタイプのインスタンスを任意に作成することはできません。その方法がわからないため、任意のタイプはリテラルタイプではない可能性があります。しかし、私たちは知っている特定のタイプのインスタンスを作成することができます:

template <typename> struct tag { }; // <== this one IS literal

template <typename T, typename V>
struct get_index;

template <typename T, typename... Ts> 
struct get_index<T, std::variant<Ts...>>
    : std::integral_constant<size_t, std::variant<tag<Ts>...>(tag<T>()).index()>
{ };

つまり、variant<A, B, C>Bのインデックスを見つけるには、variant<tag<A>, tag<B>, tag<C>>tag<B>を作成し、そのインデックスを見つけます。

これは、特殊なタイプでのみ機能します。

12
Barry

Fold式でこれを行うこともできます:

template <typename T, typename... Ts>
constexpr size_t get_index(std::variant<Ts...> const&) {
    size_t r = 0;
    auto test = [&](bool b){
        if (!b) ++r;
        return b;
    };
    (test(std::is_same_v<T,Ts>) || ...);
    return r;
}

Fold式は、最初に型に一致したときに停止し、その時点でrの増分を停止します。これは、重複するタイプでも機能します。タイプが見つからない場合、サイズが返されます。この場合、return関数にreturnがないと形式が正しくないため、これをconstexprにしないように簡単に変更できます。

variantのインスタンスを取得したくない場合、ここでの引数はtag<variant<Ts...>>

3
Barry

これを行う1つの楽しい方法は、_variant<Ts...>_を受け取り、それをカスタムクラス階層に変換することです。このクラス階層はすべて、特定の静的メンバー関数を実装し、クエリできる結果は異なります。

つまり、_variant<A, B, C>_を指定して、次のような階層を作成します。

_struct base_A {
    static integral_constant<int, 0> get(tag<A>);
};
struct base_B {
    static integral_constant<int, 1> get(tag<B>);
};
struct base_C {
    static integral_constant<int, 2> get(tag<C>);
};
struct getter : base_A, base_B, base_C {
    using base_A::get, base_B::get, base_C::get;
};
_

そして、decltype(getter::get(tag<T>()))はインデックスです(またはコンパイルされません)。うまくいけば、それは理にかなっています。


実際のコードでは、上記は次のようになります。

_template <typename T> struct tag { };

template <std::size_t I, typename T>
struct base {
    static std::integral_constant<size_t, I> get(tag<T>);
};

template <typename S, typename... Ts>
struct getter_impl;

template <std::size_t... Is, typename... Ts>
struct getter_impl<std::index_sequence<Is...>, Ts...>
    : base<Is, Ts>...
{
    using base<Is, Ts>::get...;
};

template <typename... Ts>
struct getter : getter_impl<std::index_sequence_for<Ts...>, Ts...>
{ };
_

そして、ゲッターを確立したら、実際にそれを使用する方がはるかに簡単です。

_template <typename T, typename V>
struct get_index;

template <typename T, typename... Ts>
struct get_index<T, std::variant<Ts...>>
    : decltype(getter<Ts...>::get(tag<T>()))
{ };
_

これは、タイプが異なる場合にのみ機能します。独立した型を処理する必要がある場合は、線形検索が最善でしょう。

_template <typename T, typename>
struct get_index;

template <size_t I, typename... Ts> 
struct get_index_impl
{ };

template <size_t I, typename T, typename... Ts> 
struct get_index_impl<I, T, T, Ts...>
    : std::integral_constant<size_t, I>
{ };

template <size_t I, typename T, typename U, typename... Ts> 
struct get_index_impl<I, T, U, Ts...>
    : get_index_impl<I+1, T, Ts...>
{ };

template <typename T, typename... Ts> 
struct get_index<T, std::variant<Ts...>>
    : get_index_impl<0, T, Ts...>
{ };
_
2
Barry

私の二人 セント ソリューション:

template <typename T, typename... Ts>
constexpr std::size_t variant_index_impl(std::variant<Ts...>**)
{
    std::size_t i = 0; ((!std::is_same_v<T, Ts> && ++i) && ...); return i;
}

template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T>(static_cast<V**>(nullptr));

template <typename T, typename V, std::size_t... Is>
constexpr std::size_t variant_index_impl(std::index_sequence<Is...>)
{
    return ((std::is_same_v<T, std::variant_alternative_t<Is, V>> * Is) + ...);
}

template <typename T, typename V>
constexpr std::size_t variant_index_v = variant_index_impl<T, V>(std::make_index_sequence<std::variant_size_v<V>>{});

タイプを含まない、またはタイプが重複しているルックアップでハードエラーが発生する場合は、静的アサートを以下に示します。

    constexpr auto occurrences = (std::is_same_v<T, Ts> + ...);
    static_assert(occurrences != 0, "The variant cannot have the type");
    static_assert(occurrences <= 1, "The variant has duplicates of the type");
0
Nikita Kniazev