Overload resolution: assignment of empty braces
Why is
operator=(int)
selected here, instead of "ambiguous" or the other one?
{}
to int
is the identity conversion ([over.ics.list]/9). {}
to S
is a user-defined conversion ([over.ics.list]/6) (technically, it's {}
to const S&
, and goes through [over.ics.list]/8 and [over.ics.ref] first before coming back to [over.ics.list]/6).
The first wins.
Is there a tidy workaround?
A variation of the trick std::experimental::optional
pulls to make t = {}
always make t
empty.
The key is to make operator=(int)
a template. If you want to accept int
and only int
, then it becomes
template<class Int, std::enable_if_t<std::is_same<Int, int>{}, int> = 0>
S& operator=(Int t) { a = t; return *this; }
Different constraints can be used if you want to enable conversions (you'd probably also want to take the argument by reference in that case).
The point is that by making the right operand's type a template parameter, you block t = {}
from using this overload - because {}
is a non-deduced context.
...without changing
S
?
Does template<class T> T default_constructed_instance_of(const T&) { return {}; }
and then s = default_constructed_instance_of(s);
count?
Overload resolution with an empty brace initializer: pointer or reference?
Oh, this is nasty.
Per [over.ics.list]p4 and p7:
4 Otherwise, if the parameter is a non-aggregate class
X
and overload resolution per 13.3.1.7 chooses a single best constructor ofX
to perform the initialization of an object of typeX
from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. [...][...]
6 Otherwise, if the parameter is a reference, see 13.3.3.1.4. [Note: The rules in this section will apply for initializing the underlying temporary for the reference. -- end note] [...]
[...]
7 Otherwise, if the parameter type is not a class:
[...]
(7.2) -- if the initializer list has no elements, the implicit conversion sequence is the identity conversion. [...]
The construction of a const std::pair<int,int>
temporary from {}
is considered a user-defined conversion. The construction of a const std::pair<int,int> *
prvalue, or a const int *
prvalue, or a const int
temporary object are all considered standard conversions.
Standard conversions are preferred over user-defined conversions.
Your own find of CWG issue 1536 is relevant, but mostly for language lawyers. It's a gap in the wording, where the standard doesn't really say what happens for initialisation of a reference parameter from {}
, since {}
is not an expression. It's not what makes the one call ambiguous and the other not though, and implementations are managing to apply common sense here.
assignment to type and empty braces. clarification on syntax
In you constructor you are defining a constructor that takes an integer and the default constructor. Remember that a constructor that provides default arguments for all of its parameters defines also the default constructor.
You can write it this way:
A (int a = 0); // a is a default parameter.
In your example:
A (int a = int {});//
The parameter a
is a default parameter initialized (not assigned) from a temporary integer which is value-initialized so because it is integer then it is value-initialized to 0 and then is used to initialize the parameter a. (a is a copy of it). Compiler optimize the code to remove the copy in many scenarios.
A (int a = int {5.6});// error
A (int a = int(5.6));// truncated to 5
Compiler variance for ambiguous copy-assignment from empty-braces
Why is nullopt_t
required to be DefaultConstructible
in the first place?
The spec requirement that nullopt_t
shall not be DefaultConstructible
is arguably, in retrospect, a mistake based on some LWG and CWG confusion around tag types, and the resolution of this confusion which came only after std::optional
was brought in from the Library Fundamentals TS Components.
First of all, the current (C++17, C++20) spec of nullopt_t
, [optional.nullopt]/2, requires [emphasis mine]:
Type
nullopt_t
shall not have a default constructor or an initializer-list constructor, and shall not be an aggregate.
and its main use is described in the previous section, [optional.nullopt]/1:
[...] In particular,
optional<T>
has a constructor withnullopt_t
as a single argument; this indicates that an optional object not containing a value shall be constructed.
Now, P0032R3 (Homogeneous interface for variant
, any
and optional
), one of the papers which was part of introducing std::optional
, has a discussion around nullopt_t
, tag types in general, and the DefaultConstructible
requirement [emphasis mine]:
No default constructible
While adapting
optional<T>
to the newin_place_t
type we found
that we cannot anymore usein_place_t{}
. The authors don't consider
this a big limitation as the user can usein_place
instead. It needs
to be noted that this is in line with the behavior ofnullopt_t
asnullopt_t{}
fails as no default constructible. Howevernullptr_t{}
seems to be well formed.Not assignable from
{}
After a deeper analysis we found also that the old
in_place_t
supportedin_place_t t = {};
. The authors don't consider this a big limitation as we don't expect that a lot of users could use this and the user can usein_place
instead.in_place_t t;
t = in_place;It needs to be noted that this is in line with the behavior of
nullopt_t
as the following compile fails.nullopt_t t = {}; // compile fails
However
nullptr_t
seems to be support it.nullptr_t t = {}; // compile pass
To re-enforce this design, there is an pending issue 2510-Tag types should not be
DefaultConstructible
Core issue 2510.
And indeed, the initial proposed resolution of LWG Core Issue 2510 was to require all tag types to not be DefaultConstructible
[emphasis mine]:
(LWG) 2510. Tag types should not be
DefaultConstructible
[...]
Previous resolution [SUPERSEDED]:
[...] Add a new paragraph after 20.2 [utility]/2 (following the header synopsis):
- -?- Type
piecewise_construct_t
shall not have a default constructor. It shall be a literal type. Constantpiecewise_construct
shall be initialized with an argument of literal type.
This resolution was superseded, however, as there were overlap with CWG Core Issue 1518, which was eventually resolved in a way that did not require tag types to not be DefaultConstructible
, as explicit
would suffice [emphasis mine]:
(CWG) 1518. Explicit default constructors and copy-list-initialization
[...]
Additional note, October, 2015:
It has been suggested that the resolution of issue 1630 went too far in allowing use of explicit constructors for default initialization, and that default initialization should be considered to model copy initialization instead. The resolution of this issue would provide an opportunity to adjust that.
Proposed resolution (October, 2015):
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
[...] For direct-initialization
or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. [...]
as long as explicit
also implied that the type was not an aggregate, which in turn was the final resolution of LWG Core Issue 2510 (based on the final resolution of CWG Core Issue 1518)
(LWG) 2510. Tag types should not be
DefaultConstructible
[...]
Proposed resolution:
[...] In 20.2 [utility]/2, change the header synopsis:
// 20.3.5, pair piecewise construction
struct piecewise_construct_t { explicit piecewise_construct_t() = default; };
constexpr piecewise_construct_t piecewise_construct{};[...]
These latter changes, however, were not brought into the proposal for std::optional
, arguably an oversight, and I would like to claim that nullopt_t
need not be required to not be DefaultConstructible
, only, like other tag types, that it should have a user-declared explicit
constructor, which bans it from a candidate for empty-braces copy-list-init both by it not being an aggregate and by the only candidate constructor being explicit
.
Which compiler is right and wrong here?
Given the LWG 2510, CWG 1518 (and other) confusion, let's focus on C++17 and beyond. In this case, GCC is arguably wrong to reject the program, whereas Clang and MSVC are correct to accept it.
Why?
Because the S& operator=(nullopt_t)
assignment operator is not viable for the assignment s = {};
, as the empty braces {}
would require either aggregate initialization or copy-list-initialization to create a nullopt_t
(temporary) object. nullopt_t
, however (by the idiomatic tag implementation: my implementation above), as per as per P0398R0 (which resolves CWG Core Issue 1518), is neither an aggregate nor does its default constructor participate in copy-list-initialization (from empty braces).
This likely falls under the following GCC bug report:
- Bug 54835 - (C++11)(DR 1518) Explicit default constructors not respected during copy-list-initialization
which was listed as SUSPENDED
in 2015-06-15, before the change in the resolution of CWG Core Issue 1630 ("resolution of issue 1630 went too far"). The ticket is now re-opened based on a ping from this Q&A.
Why {} is better candidate to be int than string for C++ overload resolution?
From over.ics.list#9.2:
if the initializer list has no elements, the implicit conversion sequence is the identity conversion. [ Example:
void f(int);
f( { } ); // OK: identity conversion— end example ]
Thus, the conversion from {}
to int
is an identity conversion, while {}
to const std::string&
is a user-defined conversion. And since the identity conversion is a better match, the overload corresponding to int
will be chosen.
Zero-reinitializing a struct in C++
What you are looking for is in [expr.ass]
A braced-init-list may appear on the right-hand side of
- an assignment to a scalar, in which case the initializer list shall have at most a single element. The
meaning ofx={v}
, where T is the scalar type of the expression x, is that ofx=T{v}
. The meaning ofx={}
isx=T{}
.- an assignment to an object of class type, in which case the initializer list is passed as the argument to
the assignment operator function selected by overload resolution (13.5.3, 13.3).
So your guess is correct. The compiler may (can in C++17 and above) be able to optimize things away but you can think of it as create a zero-initialized temporary and pass it to the operator=
.
Is assign with braces the same as call the constructor?
This is direct list initialization.
shared_ptr<int> myIntSmartPtr { my_alloc(42), my_free };
This is an example of the first syntax:
T object { arg1, arg2, ... }; (1)
The exact effect it has is therefore
List initialization is performed in the following situations:
- direct-list-initialization (both explicit and non-explicit constructors are considered)
- initialization of a named variable with a braced-init-list (that is, a possibly empty brace-enclosed list of expressions or nested braced-init-lists)
And for more detail about what that actually means:
The effects of list-initialization of an object of type T are:
... [A bunch of cases that don't apply]
Otherwise, the constructors of T are considered, in two phases:
- All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list
- If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all).
std::shared_ptr
does not have a constructor that takes an std::initializer_list
, so the second bullet point applies and it's constructed from the arguments therein.
What is the purpose of having an empty pair base class?
tl;dr This is the result of a really long series of hacks to implement the insane overload/explicit rules of std::pair
and maintain ABI compatibility. It is a bug in C++20.
Disclaimer
This is more of a "fun" ride along with the standard library authors down memory lane then some insightful language level revelation. It shows how extremely complicated C++ has became that implementing a pair, of all things, is a herculean task.
I tried my best recreating the history, but I'm not one of the authors.
Pair Primer
std::pair
is much more than simply
template<typename T, typename U>
struct pair
{
T first;
U second;
};
There are 8 different constructors listed on cppreference, and for an implementer, it's even more: every conditionally explicit constructor is actually two constructors, one for implicit, another for explicit.
Not all of these constructors participate in overload resolution, if they did, there would be ambiguity everywhere. Instead, there are many many rules governing when each does, and every combination of the aforementioned cases have to be written and disabled manually by SFINAE.
This culminated to 5 bug reports throughout the years on the constructors alone. Now about to become 6 ;)
Prologue
The first bug is about short-circuiting the checks of convertibility of the pair parameters if the types are the same.
template<typename T> struct B;
template<typename T> struct A
{
A(A&&) = default;
A(const B<T> &);
};
template<typename T> struct B
{
pair<A<T>, int> a;
B(B&&) = default;
};
Apparently, if they checked convertibility too early, the move constructor gets deleted due to the circular dependency and how B
is still incomplete within A
.
nonesuch
This however changed the SFINAE properties of pair
. In response, another fix was implemented. This implementation enabled previously invalid assignment operators, and so the assignment operators were turned off manually by changing their signatures
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
// ...
pair& operator=(
conditional_t<conjunction_v<is_copy_assignable<T>,
is_copy_assignable<U>>,
const pair&, const nonesuch&>::type)
Where nonesuch
is a dummy type that essentially makes this overload uncallable. Or is it?
no_braces_nonesuch
Unfortunately, even though you couldn't ever create a nonesuch
pair<int, int> p = {}; // succeeds
p = {}; // fails
you could still initialize it with braces. Since delete
doesn't resolve overload resolution, this is a hard failure.
The fix was to create no_braces_nonesuch
struct no_braces_nonesuch : nonesuch
{
explicit no_braces_nonesuch(const no_braces_nonesuch&) = delete;
};
The explicit
turns off participation in overload resolution. Finally, the assignment is uncallable. Or is it...?
__pair_base
v1
There is, unfortunately, another way to initialize an unknown type
struct anything
{
template<typename T>
operator T() { return {}; }
};
anything a;
pair<int, int> p;
p = a;
The authors realized they could solve this "easily" by leveraging the default generated special member functions: they could be not declared at all if you have a base that is non-assignable
class __pair_base
{
template<typename T, typename U> friend struct pair;
__pair_base() = default;
~__pair_base() = default;
__pair_base(const __pair_base&) = default;
__pair_base& operator=(const __pair_base&) = delete;
};
All unit tests passed, and things are looking bright. Unbeknownst, the shadow of an evil bug looms ominously on the horizon.
__pair_base
v2
ABI broke.
How is that even remotely possible? Empty bases are optimized out aren't they? Well, no.
pair<pair<int, int>, int> p;
Unfortunately, empty base optimization only applies if the base class subobjects are non-overlapping with other subobjects of the same type. In this case, the __pair_base
of the inner pair overlaps with the one of the outer pair.
The fix was "simple", we templatize __pair_base
to ensure they are different types.
Structural Types
C++20 came, and it requires that pair be structural types. This requires that there is no private bases.
template<pair<int, int>>
struct S; // fails
So ends our journey. This reminds me of Chandler Carruth's quick survey at cppcon: "who can build a C++ compiler in a year if they needed to?" Only current compiler writers think they could, given how complicated C++ is. Apparently, I don't even know how to implement std::pair
.
How to maintain initialization of struct as members are added?
struct EnableFlags {
bool doThingA;
bool doThingB;
bool doThingC;
bool doThingD;
// etc for several more bools
void clear() {
*this = EnableFlags();
}
};
This will create a temporary with all members set to zero and then make *this
a copy of it. So it sets all the members to zero, no matter how many there are.
This assumes that you haven't defined a default constructor that does something other than set all the flags to false. If you have no user-defined constructors then that assumption holds.
Since C++11 it's even simpler:
void clear() {
*this = {};
}
Related Topics
Erasing Vector::End from Vector
Speed Difference Between If-Else and Ternary Operator in C...
Does Clearing a Vector Affect Its Capacity
Best Practices for Recovering from a Segmentation Fault
Enumdisplaydevices VS Wmi Win32_Desktopmonitor, How to Detect Active Monitors
Using Char16_T and Char32_T in I/O
Why Is the Order of Evaluation for Function Parameters Unspecified in C++
How to Link Google Protobuf Libraries via Cmake on Linux
Error: Cannot Convert 'Const Wchar_T [13]' to 'Lpcstr {Aka Const Char*}' in Assignment
Conversion from Void* to the Pointer of the Base Class
Using Boost Adaptors with C++11 Lambdas
Braces Around String Literal in Char Array Declaration Valid? (E.G. Char S[] = {"Hello World"})
C++ Multi-Line Comments Using Backslash
Can't Compile Easy Source in C++ and Opengl (Glfw) in Linux in Netbeans