有没有办法从已知的替代方案重置std :: variant?

时间:2017-09-11 03:37:10

标签: c++ c++17 variant

我正在更新目前使用自定义等效的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));
}

1 个答案:

答案 0 :(得分:8)

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

GCC compiles 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的codegen,OTOH,还有很多不足之处,尽管isn't too bad如果使用std::terminate()(相当于断言)而不是__builtin_unreachable()

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。