Ternary allowed to call an explicit copy constructor implicitly? - c++

Consider the code below:
#include <cstdio>
struct A
{
A(){}
explicit A(const A&) {std::puts("copy");}
};
int main()
{
A a;
true ? a : A();
return 0;
}
As I understand the ternary would try to copy a and should fail since the copy constructor is explicit, however gcc compiles this just fine and creates a copy. Clang spits out an error as expected.
Is this a bug in gcc?
I'm using gcc 8.1 and clang 7.0, in c++17 mode, but I also tried all versions of gcc in compiler explorer in c++98 mode, and they all behave the same.

Clang is right to reject it, and it is indeed a GCC bug. I'll quote n4659 (closest document I have to to the C++17 standard) for simplicity.
First and foremost, the type of of the conditional expression in your example, as specified by [expr.cond] ¶6 must be a prvalue of type A.
Now, according to [expr.cond] ¶7, emphasis mine:
Lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard
conversions are performed on the second and third operands.
a must be able to undergo the lvalue-to-rvalue conversion. Which for a is specified in [conv.lval] ¶3.2 (again, emphasis mine) as
Otherwise, if T has a class type, the conversion copy-initializes the
result object from the glvalue.
Copy initialization of an A from an A, in any context, should pick a converting constructor in overload resolution ([over.match.copy] ¶1.1):
The converting constructors of T are candidate functions.
And an explicit copy constructor is not a converting constructor ([class.conv.ctor] ¶3)
A non-explicit copy/move constructor ([class.copy]) is a converting constructor.
A conforming C++ implementation cannot accept the conditional expression your wrote as well-formed.

Related

Ambiguous function call in msvc and clang but not in gcc

I wonder which compiler is compliant with the standard, i use the following code
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class AbstractBase
{
public:
virtual ~AbstractBase() {};
virtual std::string get_name() = 0;
virtual int get_number() = 0;
};
class BaseImpl : public AbstractBase
{
public:
BaseImpl() = delete; //(a)
//BaseImpl(BaseImpl&) = delete; //(b)
BaseImpl(const std::vector<std::string>& name_) : name(name_) {}
std::string get_name() override {return name.empty() ? std::string("empty") : name.front();}
private:
std::vector<std::string> name{};
};
class impl : public BaseImpl
{
public:
impl() : BaseImpl({}) {}
int get_number() override {return 42;}
};
int main()
{
std::unique_ptr<AbstractBase> intance = std::make_unique<impl>();
std::cout << intance->get_name() << " " << intance->get_number() << "\n";
return 0;
}
msvc and clang produce a compiler error while gcc is fine with this code. Link to godbold
https://godbolt.org/z/ETYGn5T1h
If one explicit delete the copy ctor of BaseImpl (uncomment (b)) all three compiler are fine. Also if one do not explicitly delete (a) the standard ctor (then no standard ctor is generated because there is an user defined ctor), all compilers are fine.
Apparently clang and msvc believe that by : BaseImpl({}) they can either call the user defined ctor, or indirectly via the default ctor the implicit copy ctor. However, since the default ctor is deleted, this ambiguity should not exist at all. However, by explicitly deleting the default ctor, the compilers seem to first assume that a default ctor exists for BaseImpl and generate an error before checking whether it is deleted.
I wonder now if this behavior is standard compliant.
Edit: I'm attaching the compiler output for the case that one don't want to or can't click on the link to the compiler explorer:
Clang and MSVC are correct, and gcc has a bug.
Defining a function as deleted is not the same as not declaring the function.
Except for move constructors, move assignment functions, and some cases of inherited constructors (see [over.match.funcs]/8), a deleted function is considered to exist for purposes of overload resolution. Nothing else in section [over] treats a deleted function specially. And we have [over.best.ics]/2, emphasis mine:
Implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. [ Note: Other properties, such as the lifetime, storage class, alignment, accessibility of the argument, whether the argument is a bit-field, and whether a function is deleted, are ignored. So, although an implicit conversion sequence can be defined for a given argument-parameter pair, the conversion from the argument to the parameter might still be ill-formed in the final analysis. — end note ]
So within impl() : BaseImpl({}) {}, the BaseImpl initializer uses overload resolution to select the BaseImpl constructor used to initialize the base class subobject. The candidates are all the constructors of BaseImpl: the intended BaseImpl(const std::vector<std::string>&), the deleted BaseImpl(), the implicitly declared copy constructor BaseImpl(const BaseImpl&), and the implicitly declared (and not deleted) move constructor BaseImpl(BaseImpl&&). At this point, BaseImpl() is not viable since the initializer has one argument. The vector constructor is viable since there is a constructor vector(std::initializer_list<std::string>) which is not explicit and can convert the {} argument to the vector type. The copy and move constructors are also viable since the BaseImpl() constructor is declared, and is not explicit, and "can" convert the {} argument to type BaseImpl. So overload resolution is ambiguous, even though some of the implicit conversion sequences use a deleted function.
When the BaseImpl() = delete; declaration is not present, BaseImpl simply doesn't have any default constructor, since the BaseImpl(const std::vector<std::string>&) declaration prevents implicit declaration of a default constructor. So there is no implicit conversion sequence for {} to BaseImpl, and the copy and move constructors of BaseImpl are not viable for the initialization BaseImpl({}). The vector constructor is the only viable function.
When you declare BaseImpl(BaseImpl&), deleted or not, this is considered a copy constructor (despite missing the usual const), so it prevents the implicit declarations of the copy constructor and move constructor. But this copy constructor is not viable for BaseImpl({}), since the reference to non-const type can't bind to the rvalue temporary BaseImpl object involved in using BaseImpl() (see [over.ics.ref]/3). So only the intended vector constructor is viable.
WIP marker
A comment by #aschepler has thrown the conclusion in doubt. See there for details or to pitch in if you're a language lawyer. I'll have a closer look tonight, then update this answer.
TL;DR
GCC is correct in a quite non-obvious way.
What is happening
The clang error message seems pretty clear:
<source>:28:14: error: call to constructor of 'BaseImpl' is ambiguous
impl() : BaseImpl({}) {}
^ ~~
<source>:14:7: note: candidate constructor (the implicit move constructor)
class BaseImpl : public AbstractBase
^
<source>:14:7: note: candidate constructor (the implicit copy constructor)
<source>:19:5: note: candidate constructor
BaseImpl(const std::vector<std::string>& name_) : name(name_) {}
^
1 error generated.
Compiler returned: 1
There are three constructors that are available for the BaseImpl({}) call -- the generated copy and move constructors, and the vector constructor. Clang does not know which of these to choose.
MSVCs error is a bit less straightforward:
x64 msvc v19.latest (Editor #1)
x64 msvc v19.latest
x64 msvc v19.latest
/std:c++20
123
<Compilation failed>
# For more information see the output window
x64 msvc v19.latest - 2681ms
Output of x64 msvc v19.latest (Compiler #1)
example.cpp
<source>(28): error C2259: 'BaseImpl': cannot instantiate abstract class
<source>(14): note: see declaration of 'BaseImpl'
<source>(28): note: due to following members:
<source>(28): note: 'int AbstractBase::get_number(void)': is abstract
<source>(11): note: see declaration of 'AbstractBase::get_number'
Compiler returned: 2
What happens here is that MSVC attempts to call the copy or move constructor, that for this it attempts to create a temporary of type BaseImpl from the initializer {}, and that this fails because BaseImpl is abstract before it fails because the default constructor is deleted (so you don't get an error message about that).
GCC does not consider the copy and move ctors even if they are explicitly added and so just constructs a vector and compiles fine.
What should happen
Let's dive into the standard. In particular, let's have a look at [dcl.init.general]. I'll omit non-matching parts of the standard language and denote that with (...).
First note that according to [dcl.init.general] (15)
15 The initialization that occurs
(15.1) — for an initializer that is a parenthesized expression-list or a braced-init-list,
(...)
is called direct-initialization.
There follows in [dcl.init.general] (16) a long list of conditions for what happens in initialization. The relevant here is (16.6)
(16.6) — Otherwise, if the destination type is a (possibly cv-qualified) class type:
(...)
(16.6.2) — Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (12.4.2.4), and the best one is chosen through overload resolution (12.4). Then:
(16.6.2.1) — If overload resolution is successful, the selected constructor is called to initialize the object, with the initializer expression or expression-list as its argument(s).
(...)
(16.6.2.3) — Otherwise, the initialization is ill-formed.
What this boils down to is: we look for all applicable constructors and attempt to choose a correct one. If that works, we use it, otherwise it's an error.
So let's take a look at overload resolution in [over.match.ctor]. Here it states that
1 When objects of class type are direct-initialized (9.4), (...), overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. (...). The argument list is the expression-list or assignment-expression of the initializer.
So our set of candidate functions are the generated copy and move ctors as well as the vector ctor. Next step is checking which of these are viable according to [over.match.viable]. This means first checking that the number of arguments in the call fits the candidate functions (true for all candidates) and then that
4 Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence (12.4.4.2) that converts that argument to the corresponding parameter of F. If the parameter has reference type, the implicit conversion sequence includes the operation of binding the reference, and the fact that an lvalue reference to non-const cannot be bound to an rvalue and that an rvalue reference cannot be bound to an lvalue can affect the viability of the function (see 12.4.4.2.5).
An implicit conversion sequence is, according to [over.best.ics.general],
3 A well-formed implicit conversion sequence is one of the following forms: >
(3.1) — a standard conversion sequence (12.4.4.2.2),
(3.2) — a user-defined conversion sequence (12.4.4.2.3), or
(3.3) — an ellipsis conversion sequence (12.4.4.2.4).
where a standard conversion sequence is chiefly concerned with stuff like int to long, lvalue to rvalue, ref to const ref etc. We're interested in user-defined conversion sequences here, which are
1 A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user- defined conversion (11.4.8) followed by a second standard conversion sequence. If the user-defined conversion is specified by a constructor (11.4.8.2), the initial standard conversion sequence converts the source type to the type of the first parameter of that constructor. (...)
2 The second standard conversion sequence converts the result of the user-defined conversion to the target type for the sequence; any reference binding is included in the second standard conversion sequence. (...).
(...)
There is quite definitely a user-defined conversion sequence from {} to std::vector<std::string>. Because BaseImpl's default constructor is deleted, there is not a user-defined conversion sequence from {} to BaseImpl; this would require two user-defined conversions: one to std::vector<std::string> and another to BaseImpl.
So of the three candidate constructors, only the std::vector<std::string> constructor is viable and eligible for overload resolution and should be chosen. GCC does this, and unless I made an error in my analysis, MSVC and clang have a bug.

Copy constructor elision for direct initialization when the argument is converted to the destination type

This question is about the wording of the c++ standard.
All compilers, and I think this is what should be, elide the copy constructor for the initialization of the object b bellow (assembly here):
struct B;
struct A{
operator B();
};
struct B{
B(const B&);
B(B&&);
};
void test(A a){
B b(a);
}
But when I read the standard, I do not see why this elision shall happen (bold mine) [dcl.init]/17.6.2:
Otherwise, if the initialization is direct-initialization, [...], constructors are considered.
The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s).
If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
It is specifically said that the constructor is called. But no compiler does it.
I imagine I am missing something or not reading correctly the standard. How should I read the standard?
This contrast with the previous and next paragraphs of the standard that specifically mandate copy elision [dcl.init]/17.6.1:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.
and [dlc.init]/17.6.3:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type [...]
The function selected is called with the initializer expression as its argument; [...]
The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
Where the last sentence send me back to [dcl.init]/17.6.1 which would also implies the copy elision.
#T.C. answered in a comment, this is core langugage issue CWG2327.

why `S x({})` invoke default constructor in GCC 7/C++1z mode only?

In the following snippet, GCC 7 with C++1z mode invokes the default constructor, but GCC/C++14 and Clang/C++14,C++1z invoke the initializer-list constructor.
Is this behavior affected by any C++1z Specifiation change (possibly Guaranteed copy elision?), or GCC bug?
#include <cstdio>
#include <initializer_list>
struct S {
S() { std::printf("DEF "); } // (1)
S(std::initializer_list<int> il) // (2)
{ std::printf("L=%zu ", il.size()); }
};
int main() {
S x({});
}
Output:
gcc 7.1.0/-std=c++14: L=0
gcc 7.1.0/-std=c++1z: DEF
Clang HEAD 5.0.0/-std=c++14 and c++1z: L=0
I think this is a gcc bug (submitted as 80804). The order of rules for [dcl.init] in C++17 is:
If the destination type is a (possibly cv-qualified) class type:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.
That first bullet does not apply. The initializer expression here is {}, which isn't even an expression so it doesn't even have a cv-unqualified type to compare against S. This bullet would apply if we had written S x(S{}) instead.
Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution. The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
This is direct-initialization, so constructors are considered as per [over.match.ctor], which just tells is to overload on the constructors. Since there is a std::initializer_list constructor, that one gets priority per [over.ics.rank], so that one is selected.
The only difference between C++14 and C++17 here is the introduction of that first bullet - which doesn't apply anyway, so the behavior should be the same.

Converting a stream to bool doesn't work on another compiler

Why with libstdc++ this works but with libc++ it fails? On gcc it also works:
bool b = std::cin;
You should add the language standard and compiler you compile with.
Until C++11, std::basic_ios had operator void*, since C++11 it has explicit operator bool instead.
The second one is explicit, meaning an implicit conversion like in your example cannot use it.
libstdc++ from the GNU project still unconditionally contains the pre-C++ conversion (Version 4.9.1):
operator void*() const
{ return this->fail() ? 0 : const_cast<basic_ios*>(this); }
The bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56193 is RESOLVED-FIXED since 2014-09-24, so the next release should be corrected.
According to the C++ Standard (13.3.1.5 Initialization by conversion function, p.#1)
The conversion functions of S and its base classes are considered.
Those non-explicit conversion functions that are not hidden within S
and yield type T or a type that can be converted to type T via a
standard conversion sequence (13.3.3.1.1) are candidate functions. For
direct-initialization, those explicit conversion functions that are
not hidden within S and yield type T or a type that can be converted
to type T with a qualification conversion (4.4) are also candidate
functions.
Class std::basic_ios has explicit conversion function operator bool. As
this declaration
bool b = std::cin;
does not use the direct initialization (there is the copy initialization) then it seems it is a bug of the compiler, that is the declaration shall not be compiled.

In copy-initialization, is the call to the copy constructor explicit or implicit?

class AAA {
public:
explicit AAA(const AAA&) {}
AAA(int) {}
};
int main() {
AAA a = 1;
return 0;
}
In the above code, as I understand, though elided in most cases, the copy constructor is still semantically required to be called. My question is, is the call explicit or implicit? For a long time I have the conclusion in my mind that the call to AAA::AAA(int) is implicit but the call to the copy constructor is not. Today I accidentally got g++ to compile the above code and it reported error. (VC12 compiles OK.)
In section 8.5 of the standard:
If the destination type is a (possibly cv-qualified) class type:
If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source
type is the same class as, or a derived class of, the class of the
destination, constructors are considered. The applicable constructors
are enumerated (13.3.1.3), and the best one is chosen through overload
resolution (13.3). The constructor so selected is called to initialize
the object, with the initializer expression or expression-list as its
argument(s). If no constructor applies, or the overload resolution is
ambiguous, the initialization is ill-formed.
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source
type to the destination type or (when a conversion function is used)
to a derived class thereof are enumerated as described in 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
The bolded direct-initialize in the above quotes means the call to copy constructor is explicit, right? Is g++ wrong or my interpretation of the standard wrong?
Looks like this bug: g++ fails to call explicit constructors in the second step of copy initialization
g++ fails to compile the following code
struct X
{
X(int) {}
explicit X(X const &) {}
};
int main()
{
X x = 1; // error: no matching function for call to 'X::X(X)'
}
The second step of a copy initialization (see 8.5/16/6/2) is a
direct-initialization where explicit constructors shall be considered
as candidate functions.
Looks like copy constructor is never called . Only constructor is called . The following code may call copy constructor
AAA a = 1;
AAA ab = a;
Not sure why G++ is compiling it .