【问题标题】:Get index by type in std::variant在 std::variant 中按类型获取索引
【发布时间】:2019-02-17 13:48:39
【问题描述】:

标准库中是否有一个实用程序可以获取std::variant 中给定typeindex?还是我应该为自己做一个?也就是说,我想在std::variant<A, B, C> 中获取B 的索引并返回1

相反的操作有std::variant_alternative。当然,std::variant 的列表中可能有许多相同的类型,所以这个操作不是双射,但对我来说不是问题(我可以在列表中第一次出现类型,或者在 @ 上具有唯一类型987654328@列表)。

【问题讨论】:

  • 你不是在找std::variant::index()吗?
  • @NathanOliver 我相信 OP 是在类型级别上工作,而不是在值级别上工作,所以这是行不通的。类似get_index_of<B, std::variant<A, B, C>> 返回1
  • @Justin 啊,这很有道理。这可能就是他们想要的。
  • 对于std::tuple 的好问题,恕我直言

标签: c++ c++17 c++-standard-library


【解决方案1】:

另一种看法:

#include <type_traits>

namespace detail {
    struct count_index {
        std::size_t value = 0;
        bool found = false;
    
        template <typename T, typename U>
        constexpr count_index operator+(const std::is_same<T, U> &rhs)
        {
            if (found)
                return *this;
    
            return { value + !rhs, rhs};
        }
    };
}

template <typename Seq, typename T>
struct index_of;

template <template <typename...> typename Seq, typename... Ts, typename T>
struct index_of<Seq<Ts...>, T>: std::integral_constant<std::size_t, (detail::count_index{} + ... + std::is_same<T, Ts>{}).value> {
    static_assert(index_of::value < sizeof...(Ts), "Sequence doesn't contain the type");
};

然后:

#include <variant>

struct A{};
struct B{};
struct C{};
using V = std::variant<A, B, C>;

static_assert(index_of<V, B>::value == 1);

或者:

static_assert(index_of<std::tuple<int, float, bool>, float>::value == 1);

查看godbolt:https://godbolt.org/z/7ob6veWGr

【讨论】:

    【解决方案2】:

    对于 Boost.Mp11,这是一个简短的单行:

    template<typename Variant, typename T>
    constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
    

    完整的example:

    #include <variant>
    #include <boost/mp11/algorithm.hpp>
    
    using namespace boost::mp11;
    
    template<typename Variant, typename T>
    constexpr size_t IndexInVariant = mp_find<Variant, T>::value;
    
    int main()
    {
        using V = std::variant<int,double, char, double>;
        static_assert(IndexInVariant<V, int> == 0);
        // for duplicates first idx is returned
        static_assert(IndexInVariant<V, double> == 1);
        static_assert(IndexInVariant<V, char> == 2);
        // not found returns ".end()"/ or size of variant
        static_assert(IndexInVariant<V, float> == 4); 
        // beware that const and volatile and ref are not stripped
        static_assert(IndexInVariant<V, int&> == 4); 
        static_assert(IndexInVariant<V, const int> == 4); 
        static_assert(IndexInVariant<V, volatile int> == 4); 
    }
    

    【讨论】:

      【解决方案3】:

      另一种实现Barry's answer的有趣方式,使用Boost.Mp11

      template<class T, class V>
      using get_index = std::integral_constant<
        std::size_t,
        mp11::mp_transform<mp11::mp_list, V>(mp11::mp_list<T>{}).index()
      >;
      

      这也适用于Boost.Variant2,以防您还不能使用 C++17。

      【讨论】:

        【解决方案4】:

        我的两个 cents 解决方案:

        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。例如,您可以通过乘以 Is+1 然后减去 1 来修复第二个问题。但是重复问题需要添加另一个本地标志或其他东西,现在变得复杂的imo...
        • 具有重复类型的变体是奇怪的野兽(它违背了 sum 类型的性质),默认情况下你只能有一个具有重复类型值的变量,或者使用in_place_index_t&lt;I&gt; 构造它。因此,您的答案中提出的 .index() 技巧将无法编译。此外,变体不能处于“缺席”状态。
        • “不编译”是比“给出错误/误导性答案”更好的选择。我也不是在谈论缺席状态,而是在谈论缺席类型:您的variant_index_v&lt;int, variant&lt;double&gt;&gt;variant_index_v&lt;double, variant&lt;double&gt;&gt; 都是0
        • 也不编译匹配 get&lt;int&gt;(make_tuple(1,1)) 所做的,例如。
        • 你知道它可以用静态断言来修复:) 静态断言将比没有找到合适的构造函数的编译器错误更有意义。至于我,用重复类型实例化变体不应该成功,因为这是一个逻辑错误,是标准中的漏洞。
        【解决方案5】:

        您也可以使用折叠表达式来做到这一点:

        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;
        }
        

        第一次匹配类型时折叠表达式停止,此时我们停止递增r。这甚至适用于重复类型。如果找不到类型,则返回大小。在这种情况下,如果更可取的话,可以很容易地将其更改为 not return,因为在 constexpr 函数中缺少 return 是不正确的。

        如果您不想获取variant 的实例,则此处的参数可以改为tag&lt;variant&lt;Ts...&gt;&gt;

        【讨论】:

          【解决方案6】:

          我找到了this tuple 的答案并稍作修改:

          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 if 作为结构的情况下以旧方式进行操作。

          【讨论】:

            【解决方案7】:

            我们可以利用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&lt;A, B, C&gt; 中找到B 的索引,我们用tag&lt;B&gt; 构造一个variant&lt;tag&lt;A&gt;, tag&lt;B&gt;, tag&lt;C&gt;&gt; 并找到它的索引。

            这仅适用于不同的类型。

            【讨论】:

            • 这很聪明,呵呵。
            • 很好的解决方案!
            • 只是想知道:std::declval 不会代替 tag 吗?
            • @R2RT 不。这不能解决某些类型不是文字类型的问题,而且无论如何您都不能在评估的上下文中使用declval
            • 不幸的是,在 MSVS2017 中出现了问题。 Any ideas? :)
            【解决方案8】:

            一种有趣的方法是使用您的variant&lt;Ts...&gt; 并将其转换为自定义类层次结构,所有这些层次结构都实现了具有不同结果的特定静态成员函数,您可以查询。

            换句话说,给定variant&lt;A, B, C&gt;,创建一个如下所示的层次结构:

            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&lt;T&gt;())) 是索引(或不编译)。希望这是有道理的。


            在实际代码中,上面变成:

            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...>
            { };
            

            一旦你建立了一个getter,实际使用它就简单多了:

            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...>
            { };
            

            【讨论】:

            • 感谢您展示没有 if constexpr 的实现!
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-01-22
            • 2019-05-13
            • 1970-01-01
            相关资源
            最近更新 更多