15

Update: This is a C++ standard defect, which is fixed in C++20 (P0608R3). Also, VS 2019 16.10 has fixed this bug with /std:c++20.

MSVC 19.28 rejects the following code but gcc 10.2 accepts it and outputs true false

#include <iostream>
#include <variant>

int main()
{
    std::variant<long long, double> v{ 0 };
    std::cout << std::boolalpha << std::holds_alternative<long long>(v) << ' ' << std::holds_alternative<double>(v) << std::endl;
}

According to cppreference:

  1. Converting constructor. Constructs a variant holding 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; Direct-initializes the contained value as if by direct non-list-initialization from std::forward<T>(t).

And the question is converted to which function of F(long long) and F(double) is selected agianst argument 1 by overload resolution.

Converting int to long long is an integral conversion (supposing sizeof(long long) is bigger than sizeof(int)) and converting int to double is an floating-integral conversion, neither ranks higher that the other. So the call is ambiguous and the program is ill-formed.

MSVC does rejected the code as I expected but to my surprise, gcc accepts it. Besides, there is also a similar example on cppreference:

std::variant<std::string> v("abc"); // OK
std::variant<std::string, std::string> w("abc"); // ill-formed
std::variant<std::string, const char*> x("abc"); // OK, chooses const char*
std::variant<std::string, bool> y("abc"); // OK, chooses string; bool is not a candidate
/* THIS ONE -> */ std::variant<float, long, double> z = 0; // OK, holds long
                                         // float and double are not candidates 

So my question is: is gcc or MSVC non-conformance, or my understanding is wrong?

3
  • "supposing sizeof(long long) is bigger than sizeof(int)", Have you checked that in both configuration? Commented Jan 1, 2021 at 14:24
  • FWIW, clang seems to accept it as well. Commented Jan 1, 2021 at 14:27
  • @Jarod42 I'm pretty sure.on my system (Windows 10). Commented Jan 1, 2021 at 14:38

1 Answer 1

5

In the quoted rule, an overload is considered only if copy-list-initialization for the candidate type works from the argument type. This check doesn’t (can’t) consider the constant-expression status of the argument, so int to any floating-point type is a narrowing conversion and is disallowed by list-initialization (despite the fact that on typical implementations double can exactly represent every value of int). GCC (i.e., libstdc++) is therefore correct to disregard the double alternative.

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

3 Comments

The stadard wording is T_i x[] = { std::forward<T>(t) }; which considers the value of the argument. So I'm afraid you are wrong.
Although list initialization prohibits narrowing conversion, but std::forward effectively performs an explicit conversion. For example, both float f1[] = { std::forward<float>(1) }; and float f2[] = { std::forward<float>(0xffffffffffffffffuLL) }; are valid.
@方圆圆: It’s float f[]={std::forward<int>(t)}; that is checked: T is the argument type, not the fictitious parameter (i.e., alternative) type. Similarly, t is the parameter, not the argument, so it’s never a constant expression; detecting that the argument is a constant expression is unimplementable.

Your Answer

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.