Move constructor template elision - c++

For what reason move constructor call is not elided in following code?
struct Foo {
Foo() = default;
Foo(const Foo&) { cout << "Foo(const Foo&)" << endl; }
template<class T> Foo(T&&) { cout << "Foo<T>(T&&)" << endl; }
};
auto f = Foo{};
Output: Foo<T>(T&&)
Checked on clang 3.3, g++ 4.9.
Adding defaulted or user-defined move constructor results in no output at all. Why the call to move constructor template is not elided by the compiler? And why the call to non-template one gets elided even if it's user-defined (i.e. how does the compiler know that it has no side-effects and can be safely elided)?

To be a move constructor and thus candidate for elision, the constructor must not be a template instantiation (similarly, the fact that a template can generate a constructor with the same signature will not prevent the defaulted version to be generated).
In your example, the presence of the explicit copy constructor prevent the implicit generation of a default move constructor, so the constructor template is instantiated (and is not a move constructor even if it share the signature).
If you add an explicit (default or not) move constructor, it will be used (and can -- but not must -- be elided).

The reason is because copy and move constructors are defined to be non-template (12.8/2, 12.8/3), copy/move elision rules do not apply for this constructor template (12.8/31).
The addition of defaulted or user defined move constructor (in this case) results in different output (and move elision) because user-defined copy constructor prevents compiler from implicitly generating a default one, thus the answer was wrong in first place:
12.8/7
If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class
definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4).

Related

C++ constexpr inheriting constructor

The following code compiles with GCC 8.2 but not with Clang 6.0.1:
// A struct named Foo.
struct Foo
{
// Data member of type 'int'.
int val;
// Default constructor (constexpr).
constexpr Foo() noexcept : val(0) {}
};
// A struct named Bar.
struct Bar : Foo
{
// Make use of the constructors declared in Foo.
using Foo::Foo;
// A constructor taking an object of type Foo.
// COMMENTING THIS CONSTRUCTOR SOLVE THE COMPILATION ISSUE.
constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
};
// A struct named Test.
struct Test
{
// Data member of type 'Bar'.
Bar bar;
// A defaulted default constructor.
constexpr Test() noexcept = default;
};
// Main function.
int main() { return 0; }
Clang fails with the following message:
error: defaulted definition of default constructor is not constexpr
constexpr Test() noexcept = default;
I would like to understand why Clang is rejecting this code.
It looks like clang is relying on pre C++17 wording from C++14 section [class.inhctor]p3:
For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the complete class where the using-declaration appears or the constructor would be a default, copy, or move constructor for that class. Similarly, for each constructor template in the candidate set of inherited constructors, a constructor template is implicitly declared with the same constructor characteristics unless there is an equivalent user-declared constructor template ([temp.over.link]) in the complete class where the using-declaration appears. [ Note: Default arguments are not inherited. An exception-specification is implied as specified in [except.spec]. — end note ]
So in C++14:
using Foo::Foo;
means Bar does not inherit Foo's default constructor and Bar does not have a default constructor since it is inhibited by your declaration of:
constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
Adding a default constructor to Bar fixes the problem see it live:
constexpr Bar() = default ;
The wording was changed in C++17 with the paper p0136r1: Rewording inheriting constructors (core issue 1941 et al) which was can see was accepted from Changes between C++14 and C++17 DIS
The following papers were moved at committee meetings, but their contents are too specific to call out as separate features: N3922, N4089, N4258, N4261, N4268, N4277, N4285, P0017R1, P0031R0, P0033R1, P0074R0, P0136R1, P0250R3, P0270R3, P0283R2, P0296R2, P0418R2, P0503R0, P0509R1, P0513R0, P0516R0, P0517R0, P0558R1, P0599R1, P0607R0, P0612R0
we can see p0136r1 removed [class.inhctor]:
Remove 12.9 class.inhctor, "Inheriting constructors".
I don't see any wording in p0136r1 that would restrict this case any more. The list of defect reports does not specifically cover this case but the wording changes seem consistent.
So it looks like gcc is correct here and we have a potential clang bug.
gcc 7 release notes
We also obtain a diagnostic in gcc pre 7.x (see it live). If we look at the gcc 7 release notes we see:
The default semantics of inherited constructors has changed in all modes, following P0136. Essentially, overload resolution happens as if calling the inherited constructor directly, and the compiler fills in construction of the other bases and members as needed. Most uses should not need any changes. The old behavior can be restored with -fno-new-inheriting-ctors, or -fabi-version less than 11.
Which seems to confirm the initial conclusion. If we use -fno-new-inheriting-ctors with a slightly modified version of your program it no longer compiles which backs up this was changed with P0136.
In C++14, default constructors can not be inherited.
§12.9 [class.inhctor] (emphasis mine)
3 For each non-template constructor in the candidate set of
inherited constructors other than a constructor having no parameters
or a copy/move constructor having a single parameter, a constructor is
implicitly declared with the same constructor characteristics unless
there is a user-declared constructor with the same signature in the
complete class where the using-declaration appears or the constructor
would be a default, copy, or move constructor for that class. ...
This basically means, that for your class Bar, the ctor will be implicitly defined - and means using Foo::Foo is not doing anything meaningful.
However, as you are having a separate constructor for Bar, this prevents the implicit definition of a default constructor.
The reason this works when you comment out your separate constexpr Bar(Foo const obj) ctor is because of
5 [ Note: Default and copy/move constructors may be implicitly
declared as specified in 12.1 and 12.8. —end note ]
§12.1/5 [class.ctor]
... If that user-written default constructor would satisfy the
requirements of a constexpr constructor (7.1.5), the
implicitly-defined default constructor is constexpr. ...
So, the implicitly declared constructor is declared as constexpr, which makes your code work and compile as expected.
You can remedy the issue by just explicitly defaulting the default ctor like this:
constexpr Bar() noexcept = default;
You can also take a look at Constexpr class: Inheritance?
The problem there is a bit different, but very similar to what you are seeing.
Sadly, I am unable to find the relevant parts in the C++17 standard. I assume the reasoning is the same, but can't find the reference to be 100% sure.

Does deleting a copy constructor or copy assignment operator count as "user declared"?

Per this presentation, if either the copy constructor or copy assignment operator is "user declared", then no implicit move operations will be generated. Does deleteing the copy constructor or copy assignment operator count as "user declared"?
struct NoCopy {
NoCopy(NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
};
Will implicit move operations be generated for the NoCopy class? Or does deleting the relevant copy operations count as "user declared" and thus inhibit implicit move generation?
If possible, I'd prefer an answer referencing the relevant parts of the standard.
According to slide 14 of your presentation, a deleted copy constructor is "user declared" thus inhibiting the move generation.
The term "user declared" doesn't have a formal definition in the standard. It is meant to be the opposite of "implicitly declared" in the context of special member functions. [dcl.fct.def.default]/4 could be a bit clearer about this fact, but the intention is there:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 12.4, 12.8), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
Both NoCopy(NoCopy&) = delete; and NoCopy& operator=(const NoCopy&) = delete; are declarations of special member functions. Since you are explicitly declaring them, as opposed to allowing the compiler to declare them implicitly, they are user-declared. Those declarations will therefore suppress the implicit declarations of the move constructor and move assignment operator per [class.copy]/9:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator,
— X does not have a user-declared destructor, and
— the move constructor would not be implicitly defined as deleted.

why the move constructor/move assignment are not implicitly declared and defined as deleted if we only define copy constructor/oper=?

As per the C++ standard 12.8.7:
If the class definition declares a move constructor or move
assignment operator, the implicitly declared copy constructor is
defined as deleted;
and 12.8.18
If the class definition declares a move constructor or move assignment
operator, the implicitly declared copy assignment operator is defined
as deleted;
I am wondering why the move constructor/move assignment are not implicitly declared and defined as deleted (c++11 standard will not generate implicitly declared move constructor/move assignment in this case), if we only defined copy constructor or copy assignment operator?
If this was the case, then using an rvalue as the source of construction or assginment would result in a compilation error instead of falling back to a copy.
A function which does not exist (obviously) does not participate in overload resolution. A function which is defined as deleted does participate in overload resolution normally; if it's chosen, the compilation results in an error.
This code compiles:
struct Normal
{
Normal() {}
Normal(const Normal &) {}
};
int main()
{
Normal n(Normal{});
}
While this code results in an error:
struct Deleted
{
Deleted() {}
Deleted(const Deleted &) {}
Deleted(Deleted&&) = delete;
};
int main()
{
Deleted d(Deleted{});
}
If the move constructor were deleted in that case, then trying to copy-initialise from an rvalue would be an error - the deleted move constructor would be a better match than the copy constructor.
Usually, you'd want copy-initialisation to copy, rather than be disallowed, if you haven't defined move semantics. To give that behaviour, the move constructor is simply not declared at all, so that copy-initialisation uses the copy constructor whether copying from an lvalue or an rvalue. (As long as the copy constructor takes its argument by const reference.)
You can still delete the move operations yourself, if for some reason you want the rather odd quality of only being copyable from an lvalue.

Template "copy constructor" does not prevent compiler-generated move constructor

Consider the following program and the comments in it:
template<class T>
struct S_ {
S_() = default;
// The template version does not forbid the compiler
// to generate the move constructor implicitly
template<class U> S_(const S_<U>&) = delete;
// If I make the "real" copy constructor
// user-defined (by deleting it), then the move
// constructor is NOT implicitly generated
// S_(const S_&) = delete;
};
using S = S_<int>;
int main() {
S s;
S x{static_cast<S&&>(s)};
}
The question is: why does not user-defining the template constructor (which effectively acts as a copy constructor when U = T) prevent the compiler from generating the move constructor, while, on the contrary, if I user define the "real" copy constructor (by deleting it), then the move constructor is not generated implicitly (the program would not compile)? (Probably the reason is that "template version" does not respect the standard definition of copy-constructor also when T = U?).
The good thing is that that seems to apparently be what I want. In facts, I need all the copy and move constructors and all the move and copy assignment operators that the compiler would generate implicitly as if S was simply defined as template<class U> S{}; plus the template constructor for the conversions from other S<U>. By standard, can I rely on the above definition of S to have all the mentioned stuff I need? If yes, I could then avoid to "default'ing" them explicitly.
The answer is simple - There is no (!) template copy constructor. Even if the template parameter matches the parameter of a copy constructor it is no copy constructor.
See 12.8 Copying and moving class objects
A non-template constructor for class X is a copy constructor if its
first parameter is of type X&, const X&, volatile X& or const volatile
X&, and either there are no other parameters or else all other
parameters have default arguments (8.3.6). [ Example: X::X(const X&)
and X::X(X&,int=1) are copy constructors.
Similar applies to the move constructor
There is no such thing as a templated copy constructor. The regular (non-templated) copy constructor is still generated and a better match than the templated one with the signature you provided (things get more complicated with "universal" references thrown in the mix, but that is off-topic).
Copy constructors, and move constructors, are not templated. The C++ standard is specific on what constitutes a copy constructor.
From: http://en.cppreference.com/w/cpp/language/copy_constructor
A copy constructor of class T is a non-template constructor whose first parameter is T&, const T&, volatile T&, or const volatile T&, and either there are no other parameters, or the rest of the parameters all have default values.

Move member function generation

Code:
#include <iostream>
#include <ios>
#include <string>
#include <type_traits>
#include <memory>
struct value
{
~value() = default;
std::unique_ptr<std::string> s;
};
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_move_constructible<value>::value << '\n';
std::cout << std::is_move_assignable<value>::value << '\n';
using str_ptr = std::unique_ptr<std::string>;
std::cout << std::is_move_constructible<str_ptr>::value << '\n';
std::cout << std::is_move_assignable<str_ptr>::value << '\n';
return 0;
}
Output (compiled with g++ v4.7.2, http://ideone.com/CkW1tG):
false
false
true
true
As I expect, value is not move constructible and is not move assignable because:
~value() = default;
is a user-declared destructor, which prevents the implicit generation of move members according to section 12.8 (see below).
If the destructor is removed then value is move constructible and move assignable, as I expect (http://ideone.com/VcR2eq).
However, when the definition of value is changed to (http://ideone.com/M8LHEA):
struct value
{
~value() = default;
std::string s; // std::unique_ptr<> removed
};
the output is:
true
true
true
true
value is unexpectedly move constructible and move assignable.
Am I misunderstanding or is this a compiler bug?
Background: I provided an answer to this question and was informed that Tree<> was moveable, but I am unsure and am attempting to determine for certain if it is or not.
Section 8.4.2 Explicitly-defaulted functions of the c++11 standard (draft n3337):
Explicitly-defaulted functions and implicitly-declared functions are
collectively called defaulted functions, and the implementation shall
provide implicit definitions for them (12.1 12.4, 12.8), which might mean
defining them as deleted.
A special member function is user-provided if it is user-declared and
not explicitly defaulted or deleted on its first declaration.
A user-provided explicitly-defaulted function (i.e., explicitly defaulted
after its first declaration) is defined at the point where it is
explicitly defaulted; if such a function is implicitly defined as deleted,
the program is ill-formed. [ Note: Declaring a function as defaulted after its
first declaration can provide efficient execution and concise definition while
enabling a stable binary interface to an evolving code base.—end note ]
Section 12.8 Copying and moving class objects (point 9):
If the definition of a class X does not explicitly declare a move constructor,
one will be implicitly declared as defaulted if and only if
- X does not have a user-declared copy constructor,
- X does not have a user-declared copy assignment operator,
- X does not have a user-declared move assignment operator,
- X does not have a user-declared destructor, and
- the move constructor would not be implicitly defined as deleted.
std::is_move_constructible<T> is true iff std::is_constructible<T, T&&> is true, but that doesn't imply that such a construction will call a move constructor, only that it is possible to construct the type from an rvalue of the same type. Such a construction might use a copy constructor.
When value::s is a unique_ptr the type's copy constructor and copy assignment operator are defined as deleted, because the s member is not copyable. It does not have a move constructor and move assignment operator because, as you pointed out, it has a user-declared destructor. That means it has no copy constructor and no move constructor (and no other user-defined constructors that could accept an argument of type value&&) so std::is_constructible<value, value&&> is false.
When value::s is a string the type's copy constructor and copy assignment operator are not defined as deleted, because the s member is copyable, and so value is also copyable, and a CopyConstructible type is also MoveConstructible, because it's valid in this context:
value v1;
value v2 = std::move(v1); // calls copy constructor
That means std::is_constructible<value, value&&> is true, even though it invokes the copy constructor not a move constructor.