Подтвердить что ты не робот

Есть ли способ reset std:: variant из известной альтернативы?

Я в процессе обновления базы кода, которая в настоящее время использует пользовательский эквивалент std::variant для С++ 17.

В некоторых частях кода вариант является reset из известной альтернативы, поэтому класс предоставляет метод, который утверждает, что index() находится в текущем значении, но все же непосредственно вызывает надлежащий деструктор безоговорочно.

Это используется в некоторых жестких внутренних петлях и имеет (измеренное) нетривиальное влияние производительности. Это потому, что это позволяет компилятору устранить все разрушения, когда рассматриваемая альтернатива является тривиально разрушаемым типом.

По значению лица мне кажется, что я не могу добиться этого с помощью текущей реализации std::variant<> в STL, но я надеюсь, что я ошибаюсь.

Есть ли способ выполнить это, чего я не вижу, или мне не повезло?

Изменить: в соответствии с запросом, здесь пример использования (используя пример @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 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));
}
4b9b3361

Ответ 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;
}

GCC компилирует функцию до:

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

что является оптимальным. Clang codegen, OTOH, оставляет желать лучшего, хотя не так уж плохо, если вы используете 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.