5

Consider:

#include <variant>

struct A { 
  A() = default;
  A(A&&) = delete;
};

struct B { 
  B() = delete;
  B(A&&) {};
};

int main() {
  std::variant<A, B> v{};
  v = A{};
}

MSVC accepted it, while GCC and Clang rejected it with the same error message:

opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/12.0.0/../../../../include/c++/12.0.0/variant:1465:3: error: call to deleted member function 'operator='
                operator=(variant(std::forward<_Tp>(__rhs)));
                ^~~~~~~~~
<source>:15:5: note: in instantiation of function template specialization 'std::variant<A, B>::operator=<A>' requested here
  v = A{};
    ^

Which compiler should I trust?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 1
    Did you check std::monostate? It may be suitable for this kind of situations. – Özgür Murat Sağdıçoğlu Aug 31 '21 at 11:59
  • It seems that according to the [documentation](https://en.cppreference.com/w/cpp/utility/variant/operator%3D) (converting assignment part), `T_j` should be resolved as `B`: https://godbolt.org/z/nqhxxjPM1. – Daniel Langr Aug 31 '21 at 12:31
  • @Daniel Langr. That's right, according to [variant#assign-11](https://timsong-cpp.github.io/cppwp/variant#assign-11), we seem [should](https://timsong-cpp.github.io/cppwp/variant#assign-13.3) use `operator=(variant(std​::​forward(t)))` to initialize `B`, but I'm not sure we should just perform [direct initialization](https://en.cppreference.com/w/cpp/language/direct_initialization) in such case. – 康桓瑋 Aug 31 '21 at 12:42

1 Answers1

3

EDIT

Initially, there was no language-lawyer tag, which was why I used cppreference for the analysis. However, looking into the latest draft (relevant section), I don't see anything that would make it invalid.


I believe MSVC is incorrect. According to the documentation:

  1. Converting assignment.
  • Determines the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types... in scope at the same time, except that:

    • An overload F(T_i) is only considered if the declaration T_i x[] = { std::forward<T>(t) }; is valid for some invented variable x;

With some imaginary function having forwarding reference parameter and calling it with A{} argument, A x[] = { std::forward<T>(t) }; is not valid while B x[] = { std::forward<T>(t) }; is. Consequently , T_j should be resolved as B. Live demo: https://godbolt.org/z/fM67e7oGj.

Then:

  • If *this already holds a T_j...

This does not apply, since v does not hold a B.

Next:

  • Otherwise, if std::is_nothrow_constructible_v<T_j, T> || !std::is_nothrow_move_constructible_v<T_j> is true...

This also does not apply, since this expression is false; live demo: https://godbolt.org/z/x674rnbcj (and, MSVC agrees on that: https://godbolt.org/z/5Techn8jG).

Finally:

  • Otherwise, equivalent to this->operator=(variant(std::forward<T>(t))).

However, this call does not compile with MSVC: https://godbolt.org/z/cWr4f6EhK.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • Same as I thought, but the MSVC-STL contributor thought it [should](https://github.com/microsoft/STL/issues/2171) be accepted in this case, so I added the tag of `language lawyer`. – 康桓瑋 Aug 31 '21 at 12:55
  • @康桓瑋 The analysis seems to apply also to the latest draft — [relevant section](http://eel.is/c++draft/variant#lib:operator=,variant__). I can't see anything that would disagree with _cppreference_. – Daniel Langr Aug 31 '21 at 13:04