2017-09-11 33 views
13

我正在更新一个代码库,目前使用的定制等效物为std::variant C++ 17。有没有办法从一个已知的选择重置std :: variant?

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

这用于一些紧密的内部循环,并具有(测量)不平凡的性能影响。这是因为它允许编译器在有问题的替代方法是可破坏类型时消除整个破坏。

从表面上看,我无法通过STL中的当前std::variant<>实现这个目标,但我希望我错了。

有没有办法做到这一点,我没有看到,或者我运气不好?

编辑:的要求,这里的(使用@ TC的例子为基础)的使用例子:

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

感谢来自@TC的各种见解,我已登陆这个怪物trosity。它“起作用”,即使它通过跳过一些析构函数而违反了标准。然而,每一个跳过析构函数在编译时被检查为琐碎所以...:

看到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)); 
} 
+0

偶然是所有类型的变体trivially可破坏? – vu1p3n0x

+0

@ vu1p3n0x它有点儿在模板层次结构深处松动,所以答案是:有时是,有时不是。我还没有测试过编译器是否处理yes情况,但没关系,因为我希望它在混合时工作。 – Frank

+0

“*该变体正在从一个已知的替代重置*”这是什么意思,确切地说?你能否使用你的旧型号提供代码来证明这一点? –

回答

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

这是最佳。铛的代码生成,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。

+1

作为参考,是的,当使用std :: terminate方法时,MSVC(2017)实际上保持对'std :: _ Variant_base :: _ Reset'的调用,即使使用最积极的优化选项。这是...不是很好... – Frank

+0

我upvoted的答案,但不会接受它。我宁愿有一个解决方案在代码中执行此操作(就像我曾经拥有过的那样),而不是希望编译器为我优化它。我明白,虽然现在可能不可能,但... – Frank

+0

@Frank嗯,如果你想这样做的话,总是会有一些琐碎的破坏和放置新东西的方法...... –

相关问题