【问题标题】:Default constructing an std::variant from index默认从索引构造 std::variant
【发布时间】:2020-03-06 12:31:04
【问题描述】:

当索引仅在运行时已知时,从所需类型的索引默认构造 std::variant 的最简单方法是什么?换句话说,我想写:

const auto indx = std::variant<types...>{someobject}.index();
//...somewhere later, indx having been passed around...
std::variant<types...> var = variant_from_index(indx);
///var is now set to contain a default constructed someobject

请注意,indx 不能设为 constexpr,因此 std::in_place_index 在这里不起作用。

这里的问题当然是因为不知道在编译时调用来自types... 的哪个构造函数,所以基本上必须构建一个包含所有可能构造函数的表(或者可能是要复制的默认构造变体)在编译时,然后在运行时访问。这里显然有一些模板魔法,但最干净的方法是什么?

我尝试了以下(on coliru),但索引序列似乎出现错误(最后打印出2 0 0),我不知道为什么:

编辑: 它的工作方式如下所示,我的 constexpr 数组初始化错误。所以现在的问题是,有没有更简洁的方法来做到这一点?

#include <variant>
#include <iostream>

using var_t = std::variant<int, float, const char *>;

//For debug
template<class ...types>
struct WhichType;

template<class T, class U>
struct default_variants;
template<class...Params, std::size_t... I>
struct default_variants<std::variant<Params...>, std::index_sequence<I...>> {
    using variant_t = std::variant<Params...>;
    //Uncomment to see the index sequence
    //WhichType<std::index_sequence<I...>> idx{};
    constexpr static variant_t variants[sizeof...(Params)]{variant_t{std::in_place_index<I>}...};
    constexpr static std::size_t indices[sizeof...(Params)]{I...};
};
template<class T>
struct default_variants_builder;
template<class...Params>
struct default_variants_builder<std::variant<Params...>> {
    using indices = std::make_index_sequence<sizeof...(Params)>;
    using type = default_variants<std::variant<Params...>, indices>;
};


int main() {
    using builder_t = typename default_variants_builder<var_t>::type;
    var_t floatvar{1.2f};
    var_t variant2 = builder_t::variants[floatvar.index()];
    std::cout << "Contained " << floatvar.index() << "; Now contains " << variant2.index() << "\n";
}

【问题讨论】:

    标签: c++ templates c++17 template-meta-programming variant


    【解决方案1】:

    对于Boost.Mp11,这基本上是一条线(一如既往):

    template <typename V>
    auto variant_from_index(size_t index) -> V
    {
        return mp_with_index<mp_size<V>>(index,
            [](auto I){ return V(std::in_place_index<I>); });
    }
    

    您对问题的描述是准确的 - 您需要一种将运行时索引转换为编译时索引的方法。 mp_with_index 为您执行此操作 - 您为其提供运行时索引和最大编译时索引(此处为 mp_size&lt;V&gt;,如果您愿意,它将给出与 std::variant_size_v&lt;V&gt; 相同的值),它将调用您的函数提供正确的常量(I 在此处具有类型integral_constant&lt;size_t, index&gt;,但index 是常量表达式除外)。

    【讨论】:

      【解决方案2】:

      这个怎么样?

      template <class Variant, std::size_t I = 0>
      Variant variant_from_index(std::size_t index) {
          if constexpr(I >= std::variant_size_v<Variant>)
              throw std::runtime_error{"Variant index " + std::to_string(I + index) + " out of bounds"};
          else
              return index == 0
                  ? Variant{std::in_place_index<I>}
                  : variant_from_index<Variant, I + 1>(index - 1);
      }
      

      See it live on Wandbox

      【讨论】:

      • 您能解释一下为什么会这样吗?如果删除最上面的 if constexpr,则不再编译。这里有什么诀窍?
      【解决方案3】:

      不确定这是否非常优雅,但我认为它有效:

      #include <variant>
      #include <iostream>
      
      template<typename V, std::size_t N = std::variant_size_v<V>>
      struct variant_by_index {
          V make_default(std::size_t i) {
              if (i >= std::variant_size_v<V>) {
                  throw std::invalid_argument("bad type index.");
              }
              constexpr size_t index = std::variant_size_v<V> - N;
              if (i == index) {
                  return std::variant_alternative_t<index, V>();
              } else {
                  return variant_by_index<V, N - 1>().make_default(i);
              }
          }
      };
      
      template<typename V>
      struct variant_by_index<V, 0> {
          V make_default(std::size_t i) {
              throw std::bad_variant_access("bad type index.");
          }
      };
      
      using var_t = std::variant<int, float, const char *>;
      
      int main() {
          variant_by_index<var_t> type_indexer;
          var_t my_var_0 = type_indexer.make_default(0);
          std::cout << "my_var_0 has int? " << std::holds_alternative<int>(my_var_0) <<  "\n";
          var_t my_var_1 = type_indexer.make_default(1);
          std::cout << "my_var_1 has float? " << std::holds_alternative<float>(my_var_1) <<  "\n";
          try {
              var_t my_var_1 = type_indexer.make_default(3);
          } catch(const std::bad_variant_access&) {
             std::cout << "Could not create with type 3.\n";
          }
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        我相信一种(有点)优雅的方式可能是使用更通用的习惯用法在运行时选择数字模板参数值,如本问题所述:

        Idiom for simulating run-time numeric template parameters?

        foo 函数将是 std::get&lt;std::size_t I&gt;(或捕获变体且不带参数的 lambda)。

        【讨论】:

          猜你喜欢
          • 2023-03-15
          • 1970-01-01
          • 2018-10-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-31
          相关资源
          最近更新 更多