【问题标题】:Is there a way to reset a std::variant from a known alternative?有没有办法从已知的替代方案中重置 std::variant ?
【发布时间】:2018-02-19 05:32:11
【问题描述】:

我正在将当前使用 std::variant 的自定义等效项的代码库更新为 C++17。

在代码的某些部分,变体正在从一个已知的替代方案中重置,因此该类提供了一个方法来断言index() 处于当前值,但仍无条件地直接调用适当的析构函数。

这用于一些紧凑的内部循环,并且具有(测量的)非平凡的性能影响。这是因为它允许编译器在所讨论的替代方案是可简单破坏的类型时消除整个破坏。

从表面上看,在我看来,我无法使用 STL 中当前的 std::variant<> 实现来实现这一点,但我希望我错了。

有没有一种我没有看到的方法来完成这个,或者我不走运?

编辑:根据要求,这是一个使用示例(使用@T.C的示例作为基础):

struct S {
    ~S();
};

using var = MyVariant<S, int, double>;

void change_int_to_double(var& v){
  v.reset_from<1>(0.0);
}

change_int_to_double 编译有效:

@change_int_to_double(MyVariant<S, int, double>&)
  mov qword ptr [rdi], 0       // Sets the storage to double(0.0)
  mov dword ptr [rdi + 8], 2   // Sets the index to 2

编辑#2

多亏了@T.C. 的各种见解,我已经登上了这个怪物。即使它通过跳过一些析构函数确实违反了标准,它也“有效”。但是,每个跳过的析构函数在编译时都会被检查为微不足道的,所以......:

在 Godbolt 上查看:https://godbolt.org/g/2LK2fa

// Let's make sure our std::variant implementation does nothing funky internally.
static_assert(std::is_trivially_destructible<std::variant<char, int>>::value, 
          "change_from_I won't be valid");

template<size_t I, typename arg_t, typename... VAR_ARGS>
void change_from_I(std::variant<VAR_ARGS...>& v, arg_t&& new_val) {
    assert(I == v.index());

    // Optimize away the std::get<> runtime check if possible.
    #if defined(__GNUC__) 
      if(v.index() != I) __builtin_unreachable();
    #else
      if(v.index() != I) std::terminate();
    #endif


    // Smart compilers handle this fine without this check, but MSVC can 
    // use the help.
    using current_t = std::variant_alternative_t<I, std::variant<VAR_ARGS...>>;
    if(!std::is_trivially_destructible<current_t>::value) {
        std::get<I>(v).~current_t();
    }
    new (&v) var(std::forward<arg_t>(new_val));
}

【问题讨论】:

  • 碰巧变体中的所有类型都可以轻易破坏?
  • @T.C.不,因为还有运行时检查,在我的版本中,cmpjne 不存在。 (虽然我会说我对编译器完成这一点印象深刻,但如果不理想的话,它可能已经足够接近了)
  • 如果你使用__builtin_unreachable()而不是terminate(),GCC会给你你想要的。不过,Clang 的代码生成器很糟糕。
  • @T.C.哦哇!太棒了,我只需要让 clang 和 MSVC 打球,但这足以让我继续前进。谢谢!

标签: c++ c++17 variant


【解决方案1】:
#include <variant>
struct S {
    ~S();
};
using var = std::variant<S, int, double>;

void change_int_to_double(var& v){
    if(v.index() != 1) __builtin_unreachable();
    v = 0.0;
}

GCCcompiles the function down to:

change_int_to_double(std::variant<S, int, double>&):
  mov QWORD PTR [rdi], 0x000000000
  mov BYTE PTR [rdi+8], 2
  ret

这是最优的。 Clang 的代码生成器 OTOH 有很多不足之处,尽管如果您使用 std::terminate()(相当于断言)而不是 __builtin_unreachable(),它是 isn't too bad

change_int_to_double(std::__1::variant<S, int, double>&): # @change_int_to_double(std::__1::variant<S, int, double>&)
  cmp dword ptr [rdi + 8], 1
  jne .LBB0_2
  mov qword ptr [rdi], 0
  mov dword ptr [rdi + 8], 2
  ret
.LBB0_2:
  push rax
  call std::terminate()

MSVC...我们先不谈 MSVC。

【讨论】:

  • 作为参考,是的,MSVC (2017) 在使用 std::terminate 方法时实际上保持对 std::_Variant_base&lt;S,int,double&gt;::_Reset 的调用,即使使用最激进的优化选项也是如此。哪个...不太好...
  • 我赞成这个答案,但现在还不接受。我宁愿有一个在代码中执行此操作的解决方案(就像我曾经拥有的那样),而不是希望编译器为我优化它。我确实知道现在可能不可能......
  • @Frank 嗯,如果你想走那条路,总有 check-trivial-destructible-and-placement-new-over-the-thing 方法......
  • 我需要一种方法来设置索引才能做到这一点。
  • @Frank 不,你放置了一个全新的variant
猜你喜欢
  • 1970-01-01
  • 2014-07-28
  • 2019-09-01
  • 2021-12-29
  • 2011-07-17
  • 2021-09-12
  • 2015-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多