Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Variant: Why does Converting constructor require sizeof...(Types) to be nonzero

Tags:

c++

c++17

This question is regarding: template<class...Types> class variant:

According to variant.variant/3, a program instantiating variant with no template arguments is ill-formed.

So far, so clear. Now I have a question regarding the converting constructor (template<class T> constexpr variant(T&& t) noexcept(see below)):

variant.variant/variant.ctor-16.1 says that the converting constructor shall not participate in overload resolution unless:

sizeof...(Types) is nonzero

(... and some other requirements I do not care about for now).

My question is, when a variant with no template arguments already makes my program ill-formed, why still care about whether my converting constructor participates in overload resolution or not?

Having a look at the MSVC and libstdc++ -implementation of variant they actually have an enable_if_t<sizeof...(_Types) != 0> at the declaration of the converting constructor. Why?

like image 956
Kilian Avatar asked Feb 04 '19 20:02

Kilian


1 Answers

The clause "sizeof...(Types) is nonzero" was added to [variant.ctor] as part of the paper: Some improvements to class template argument deduction integration into the standard library to allow variant support as well.

Relevant Excerpts:

Enable variant support

The following code fails to compile

variant<int, double> v1(3);
variant v2 = v1;  // Ill-formed! <--THIS

As this natural code is useful and its failure is confusing, we propose that it be supported. Indeed, prior to the adoption of p0510r0 banning variant<>, the above code worked as expected since variant<> occurs in some deduction guides in the overload set. As it is not clear that constructor template argument deduction was considered in adopting p0510r0, we would like to consider allowing variant<> not to produce a hard error in such cases.

Wording (Emphasis added)
Change §23.7.3.1p16 [variant.ctor] as follows:
Remarks: This function shall not participate in overload resolution unless sizeof...(Types) is nonzero, unless is_same_v<decay_t<T>, variant> is false, unless decay_t<T> is neither a specialization of in_place_type_t nor a specialization of in_place_index_t, unless is_constructible_v<Tj, T> is true, and unless the expression FUN(std::forward(t)) (with FUN being the above-mentioned set of imaginary functions) is well formed.

So std::variant v2 = v1; fails in compiler versions that do not take into account the added clause (like GCC 7.1. See DEMO) but succeeds on later versions (From GCC 7.2 onwards. See DEMO).

like image 194
P.W Avatar answered Oct 30 '22 07:10

P.W