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?

3
  • 1
    Did you check std::monostate? It may be suitable for this kind of situations. Commented Aug 31, 2021 at 11:59
  • It seems that according to the documentation (converting assignment part), T_j should be resolved as B: godbolt.org/z/nqhxxjPM1. Commented Aug 31, 2021 at 12:31
  • @Daniel Langr. That's right, according to variant#assign-11, we seem should use operator=(variant(std​::​forward<T>(t))) to initialize B, but I'm not sure we should just perform direct initialization in such case. Commented Aug 31, 2021 at 12:42

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

Same as I thought, but the MSVC-STL contributor thought it should be accepted in this case, so I added the tag of language lawyer.
@康桓瑋 The analysis seems to apply also to the latest draft — relevant section. I can't see anything that would disagree with cppreference.

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.