2017-09-11 5 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の様々な洞察のおかげで、私はこの月に上陸しました腐敗いくつかのデストラクタをスキップして標準に違反しているにもかかわらず、 "動作"します。 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

偶然によって、変種のすべてのタイプが簡単に破壊可能ですか? – vu1p3n0x

+0

@ vu1p3n0xそれはちょっとテンプレート階層内で掘り下げているので、答えは:時にははい、ときにはいいえです。私はコンパイラが「はい」のケースを処理するかどうかをテストしていませんが、混在しているときに機能するようにしても問題ありません。 – 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 

ある

がgodboltに参照してください。ただし、すべてのはスキップデストラクタは...些細なことをコンパイル時にチェックそうさ最適な。クランのコード生成は、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

参考のために、MSVC(2017)は、 'std :: terminate'アプローチを使用するとき、最も積極的な最適化オプションを使用しても、実際に' std :: _ Variant_base :: _ Reset'の呼び出しを維持します。それは...大したものではありません... – Frank

+0

私は答えをアップアップしましたが、まだそれを受け入れません。私はむしろコンパイラが私のためにそれを最適化することを望むよりもむしろ(私が持っていた)コードでこれを実行するソリューションを持っています。私は今でも可能ではないかもしれないことを理解しています... – Frank

+0

@Frankうーん、もしあなたがそうしたいのであれば、チェック - 些細な - 破壊的かつ配置的な新しいオーバー・ザ・ワンのアプローチがあります... –

関連する問題