Is default constructor elision / assignment elision possible in principle? - c++

. or even allowed by the C++11 standard?
And if so, is there any compiler that actually does it?
Here is an example of what I mean:
template<class T> //T is a builtin type
class data
{
public:
constexpr
data() noexcept :
x_{0,0,0,0}
{}
constexpr
data(const T& a, const T& b, const T& c, const T& d) noexcept :
x_{a,b,c,d}
{}
data(const data&) noexcept = default;
data& operator = (const data&) noexcept = default;
constexpr const T&
operator[] (std::size_t i) const noexcept {
return x_[i];
}
T&
operator[] (std::size_t i) noexcept {
return x_[i];
}
private:
T x_[4];
};
template<class Ostream, class T>
Ostream& operator << (Ostream& os, const data<T>& d)
{
return (os << d[0] <<' '<< d[1] <<' '<< d[2] <<' '<< d[3]);
}
template<class T>
inline constexpr
data<T>
get_data(const T& x, const T& y)
{
return data<T>{x + y, x * y, x*x, y*y};
}
int main()
{
double x, y;
std::cin >> x >> y;
auto d = data<double>{x, y, 2*x, 2*y};
std::cout << d << std::endl;
//THE QUESTION IS ABOUT THIS LINE
d = get_data(x,y);
d[0] += d[2];
d[1] += d[3];
d[2] *= d[3];
std::cout << d << std::endl;
return 0;
}
Regarding the marked line:
Could the values x+y, x*y, x*x, y*y be written directly to the memory of d?
Or could the return type of get_data be directly constructed in the memory of d?
I can't think of a reason to not allow such an optimization. At least not for a class that has only constexpr constructors and default copy and assignment operators.
g++ 4.7.2 elides all copy constructors in this example; it seems however that assignment is always performed (even for default assignment only - as far as I can tell from the assembly that g++ emits).
The motivation for my question is the following situation in which such an optimization would greatly simplify and improve library design.
Suppose you write performance-critical library routines using a literal class. Objects of that class will hold enough data (say 20 doubles) that copies have to be kept to a minimum.
class Literal{ constexpr Literal(...): {...} {} ...};
//nice: allows RVO and is guaranteed to not have any side effects
constexpr Literal get_random_literal(RandomEngine&) {return Literal{....}; }
//not favorable in my opinion: possible non-obvious side-effects, code duplication
//would be superfluous if said optimization were performed
void set_literal_random(RandomEngine&, Literal&) {...}
It would make for a much cleaner (functional programming style) design if I could do without the second function. But sometimes I just need to modify a long-lived Literal object and have to make sure that I don't create a new one and copy-assign it to the one I want to modify. The modification itself is cheap, the copies aren't - that's what my experiments indicate.
EDIT:
Let's suppose the optimization shall only be allowed for a class with noexcept constexpr constructors and noexcept default operator=.

Elision of default copy/move assignment operators is allowed based on the general as-if rule only. That is the compiler can do it if it can ascertain that it will have no observable effect on behaviour.
In practice the as-if rule is used in general fashion to allow optimizations at intermediate representation and assembly levels. If the compiler can inline the default constructor and assignment, it can optimize them. It won't ever use the code of the copy constructor for it, but for their default implementations it should end up with the same code.
Edit: I answered before there was the code sample. Copy/move constructors are elided based on explicit permission to the compiler to do so, therefore they are elided even if they have observable effect (printing "COPY"). Assignments can only be elided based on as-if rule, but they have observable effect (printing "ASSIGN"), so the compiler is not allowed to touch them.

Does the standard allow for the elision of the assignment operator? Not in the same way as for construction. If you have any construct d = ..., the assignment operator will be called. If ... results in an expression of the same type as d, then the appropriate copy or move assignment operator will be called.
It is theoretically possible that a trivial copy/move assignment operator could be elided. But implementations are not allowed to elide anything that you could detect being elided.
Note that this is different from actual copy/move elision, because there the standard explicitly allows the elision of any constructor, whether trivial or not. You can return a std::vector by value into a new variable, and the copy will be elided if the compiler supports it. Even though it is very easy to detect the elision. The standard gives special permission to compilers to do this.
No such permission is granted for copy/move assignment. So it could only ever "elide" something that you couldn't tell the difference about. And that's not really "elision"; that's just a compiler optimization.
Objects of that class will hold enough data (say 20 doubles) that copies have to be kept to a minimum.
There's nothing stopping you from returning that Literal type right now. You will get elision if you store the object in a new variable. And if you copy assign it to an existing variable, you won't. But that's no different from a function which returns a float that you store into an existing variable: you get a copy of the float.
So it's really up to you how much copying you want to do.

There is an important drawback to what you propose: what would happen if the constructor threw? The behaviour for that case is well defined in the standard (all data members that had already been constructed are destructed in reverse order), but how would that translate into the case where we are "constructing" the object into an already existing one?
Your example is easy because the constructor cannot throw when T = double, but this is not so in the general case. You might end up with a half destructed object and undefined behaviour would ensue, even if your constructor and assignment operator were well behaved.

Jan Hudec's answer has a very good point about the as-if rule (+1 for him).
Hence, the assignment elision is allowed -- as he said -- provided that there's no observable effect. The fact that your assignment operator outputs "ASSIGN" is enough to prevent the optimization.
Notice that the situation is different for copy/move constructors because the standard allows elision for copy/move constructor even if they have observable side effects (see 12.8/31).

Related

No clang warning for returning a reference to function argument

I understand that returning a reference to a function argument could invoke undefined behaviour as in the below example. The first 'MyType' created goes out of scope after the function call and is destroyed, leading to a dangling reference.
#include <iostream>
struct MyType {
std::string data;
inline ~MyType() {
data = "Destroyed!!";
}
};
const MyType& getref(const MyType& x) {
return x;
}
int main(int argc, char *argv[]) {
const MyType& test = getref(MyType {"test"});
std::cout << test.data << std::endl;
return 0;
}
My questions are:
Why isn't there a clang warning for this? There is a warning for returning a ref to a local variable.
No matter how hard I tried allocating other things on the stack before the print, I couldn't get it to print the wrong thing without making the destructor explicitly change the data. Why is this?
Are there any valid (safe) use cases of returning a reference to an argument?
No matter how hard I tried allocating other things on the stack before the print, I couldn't get it to print the wrong thing without making the destructor explicitly change the data. Why is this?
You're invoking undefined behaviour; anything could happen at this point!
Why isn't there a clang warning for this? There is a warning for returning a ref to a local variable.
Are there any valid (safe) use cases of returning a reference to an argument?
std::max
Suppose you want the max of two values:
auto foo = std::max(x, y); // foo is a copy of the result
You might not want a copy to be returned (for performance or semantic reasons). Luckily, std::max returns a reference, allowing for both use cases:
auto foo = std::max(x, y); // foo is a copy of the result
auto& bar = std::max(x, y); // bar is a reference to the result
An example of where it matters for semantics reasons is when used in conjunction with std::swap:
std::swap(x, std::max(y, z));
Imagine that std::max returned a copy of y. Rather than swapping with x with y, x would we swapped with a copy of y. In other words, y would remain unchanged.
Assignment
A common use case of this is the assignment operator. Take this for example:
#include <iostream>
class T {
public:
T(int _x) : x(_x) { }
T& operator=(const T& rhs) { x = rhs.x; return *this; }
int getX() const { return x; }
private:
int x = 0;
};
int main() {
T instanceA(42);
T instanceB(180);
std::cout << (instanceA = instanceB).getX() << std::endl;
}
You can think of the hidden this parameter as a non-null pointer (so close enough to a reference for the purposes of this question).
Defining a copy-assignment operator as such is generally considered idomatic. One of the reasons this is so is because the automatically generated copy-assignment operator has that signature:
N4618 12.8.2.2 Copy/move assignment operator [class.copy.assign]
The implicitly-declared copy assignment operator for a class will have the form X& X::operator=(const X&)
Making this a warning would penalize canonical code! As for why one might want to do such a thing (aside from it being canonical), that's another topic...
There's no warning because a const T& is just as likely to be a reference to an lvalue as it is to be a reference to an rvalue.
void func() {
MyType mt;
const MyType& l = getref(mt);
const MyType& r = getref(MyType{});
}
Considering that getref(mt) is entirely valid, this leaves us with three options:
Emit a warning for any function that both takes and returns a (cv) T&, regardless of whether it was called with an lvalue or an rvalue. This penalises legal code, so it's not a good option. [While this would be better served by only emitting a warning if the function specifically returns its parameter, this is infeasible for the reason mentioned below. Thus, the compiler would emit an error if the parameter and return value are the same type.]
[Note that this could cause problems with perfect forwarding. For example, std::move() uses perfect forwarding to take a parameter by reference or rvalue reference, then return an rvalue reference to that parameter. Any algorithm that doesn't account for this would see it as both taking and returning a T&&.]
Emit a warning when getref() is passed an rvalue. This requires that the compiler keep the information "getref() is a potential source of UB if passed an rvalue" in memory at all times, and that it examine every call. Not only does this cause additional overhead, it effectively requires getref() to be inline (due to compilers typically being designed to only operate on a single translation unit at a time, getref() must be defined in every module where it's used for the compiler to retain this information). This is infeasible at the moment, but may become more practical with future compilers (such as if cross-module optimisation becomes standard).
Never emit a warning, on the assumption that the programmer either is wise enough to never pass getref() an rvalue, or specifically intends for getref() to be a potential source of UB. This is the most viable option, as it neither penalises legal code, nor requires a compiler capable of cross-module optimisation.
Thus, most compilers will typically choose not to emit a warning, on the assumption that the programmer knows what they're doing. Even with -Wall specified, Clang, GCC, ICC, and MSVC won't emit any warnings for this.

QVector::append() what reason for explicit copying?

template <typename T>
void QVector<T>::append(const T &t)
{
const T copy(t);
const bool isTooSmall = uint(d->size + 1) > d->alloc;
if (!isDetached() || isTooSmall) {
QArrayData::AllocationOptions opt(isTooSmall ? QArrayData::Grow : QArrayData::Default);
reallocData(d->size, isTooSmall ? d->size + 1 : d->alloc, opt);
}
if (QTypeInfo<T>::isComplex)
new (d->end()) T(copy);
else
*d->end() = copy;
++d->size;
}
What is the reason to make const T copy(t) instead of passing t by value into the method? What is the difference between this and:
template <typename T>
void QVector<T>::append(const T t)
{
const bool isTooSmall = uint(d->size + 1) > d->alloc;
if (!isDetached() || isTooSmall) {
QArrayData::AllocationOptions opt(isTooSmall ? QArrayData::Grow : QArrayData::Default);
reallocData(d->size, isTooSmall ? d->size + 1 : d->alloc, opt);
}
if (QTypeInfo<T>::isComplex)
new (d->end()) T(t);
else
*d->end() = t;
++d->size;
}
QVector requires elements to be assignable data types, which means they
must provide a default constructor, a copy constructor, and an assignment
operator.
Their version of append enforce all, while your version will not enforce copy construction if QTypeInfo<T>::isComplex is false and optimized away.
Note: QTypeInfo<T>::isComplex is resolved at compile time.
I suspect it is a legacy requirement, as QVector already existed in Qt2, which date from 1999 well before c++ standardization.
I can think of 2 possible reasons.
It follows the style of the rest of the stl, copy and swap and pass by const &. Neither of those do any good performance or const correctness wise in this situation; but it does keep the style consistent.
Because the parameter is bound to a reference, it avoids copy elision on the function call. And because the items in a vector must be assignable( ie not const ), it avoids copy elision on the assignment into the Vector
This makes the code MUCH more portable and consistent across compilers. Which is probably a big deal for the stl.
Copy Elision Wikipedia Entry
31) When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.123 This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
— when a temporary class object that has not been bound to a reference
(12.2) would be copied/moved to a class object with the same
cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move

disabled exceptions and noexcept()

std::swap is declared this way:
template <class T> void swap (T& a, T& b)
noexcept (is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value);
If I disable exceptions in my program (like with -fno-exceptions for g++) will std::swap use move operations for my custom types if they are move-enabled no matter if they are noexcept or not?
EDIT: follow-up question:
After realizing that std::swap will always use moves if my type has them, my real question is what happens to traits like is_nothrow_move_assignable<>?
Will std::vector always use moves when reallocating if my types have noexcept(true) move operations?
The noexcept-specification on swap solely tells the user where she can use swap without encountering an exception. The implementation is practically always equivalent to
auto tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
Which moves the objects around if, and only if, overload resolution selects a move assignment operator and/or constructor.
Yes. noexcept simply specifies that std::swap will not throw if T's move constructor and move assignment will not throw. It does not affect the behavior of the body of swap whatsoever - which will use T's move constructor and move assignment regardless of whether or not they throw and regardless of whether or not you compile with exceptions enabled.

Why std::vector requires operator =

I have a question about a class that we can store in vector.
What is the requirement that can be stored in a vector?
It seems that such class has to have assignment operator. But I am not sure if that's all or not.
Let me give you an example. class A has const int member. If I don't write operator =, it doesn't compile. But in this example, this operator does nothing. This program displays 10 and 20 correctly. It looks that operator = is required but not used in reality.
#include <iostream>
#include <vector>
class A {
public:
A(int a) : a_(a) {}
A& operator =(const A& a2) { return *this;} // Without this, compile fails.
void print() const {
std::cerr << a_ << std::endl;
}
private:
const int a_;
};
int main(int argc, char** argv) {
std::vector<A> v;
v.push_back(A(10));
v.push_back(A(20));
for (const A& a : v) a.print();
}
This might surprise you:
v.push_back(A(20));
v.push_back(A(10));
std::sort(begin(v), end(v));
There are aspects of vector itself that require assignability, though I don't know, offhand, which (and I can't tell by compiling your code, since my compiler doesn't complain when I remove operator=()). According to Wikipedia (which references the relevant portion of the '03 standard), elements must be CopyConstructible and Assignable.
EDIT: Coming back to this a day later, it seems forehead-slappingly obvious when std::vector requires Assignable — any time it has to move elements around. Add a call to v.insert() or v.erase(), for example, and the compile will fail.
If I don't write operator =, it doesn't compile.
That surprised me, so I had a look into the standard and I found:
Your example has an implicitly deleted copy constructor but should still compile if a conforming C++11 standard library is at hand.
The only expression that puts constraints on the type used by the vector in your example is push_back.
The push_back() method of a sequence container type X<T,A> with allocator A and value_type T, requires T to be:
CopyInsertable if an lvalue or const rvalue reference is passed
MoveInsertable if a non-const rvalue is passed
Which means it requires a valid copy constructor or (as in this case) a valid move constructor which will be implicitly present from your code. Therefore the compilation should not fail in any compiler with a valid C++11 standard library.
Operations that require the type, contained in a vector to be assignable:
Ancillary conditions
typdef std::vector<T> X;
X a,b;
X&& rv;
X::value_type t;
X::value_type&& u;
X::size_type n;
X::const_iterator p,q; // p = valid for a, q = valid and dereferencable
initializer_list<T> il;
[i,j) -> valid iterator-range
Pessimistic* list of operations
The operations, which require T to be assignable, if X is a vector, are:
Statement Requirement on T
a = b; CopyInsertable, CopyAssignable
a = rv; MoveInsertable, MoveAssignable
a = il; CopyAssignable
a.emplace(p, args); MoveInsertable, MoveAssignable
a.insert(p, t); CopyAssignable
a.insert(p, u); MoveAssignable
a.insert(p, n, t); CopyInsertable, CopyAssignable
a.insert(p, i, j); EmplaceConstructible[from *i], MoveInsertable, MoveAssignable
a.insert(p, il); -> a.insert(p, il.begin(), il.end());
a.erase(q); MoveAssignable
a.erase(q1,q2) MoveAssignable
a.assign(i,j); Assignable from *i
a.assign(il); -> a.assign(il.begin(), il.end());
a.assign(n,t) CopyAssignable
* = Pessimistic means that there may exist certain conditions for several requirements to actually come into effect. If you use one of the expressions listed above, your type T will likely be required to be assignable.
push_back on vector will make vector grow in memory, that means
old objects needs to be copied to new object via assignment operator=
hence you need assignment operator=.
The availability of copy constructor and assignment operator are guaranteed by the compiler if you don't create any of your own. (the correctness of default implementation, is a different story though). In your example, you almost don't have to overload A::opeartor=().
The problem here is not a "missing" operator=(), but that the default one cannot be used, because you have declared a const data member
const int a_;
The default compiler generated operator=() cannot assign value to a const member. So you have to overload your self:
A & A::opeartor=(const A & in)
{
*const_cast<int*>(&a_) = in.a_; // !!!you are expected to do this.
}
So, your version of A::operator=() that does nothing, although making the code compile, does't change the a_ value of the left hand operand,

Does "operator=" return type matter if I want to make the class non-copyable?

Suppose I have a class that doesn't support memberwise copying so I don't want to preserve compiler-implemented copy-constructor and assignment operator. I also don't want to implement those because either
doing so takes extra effort and I don't need those operations in my class or
those operations won't make sense in my class
so I want to prohibit them. To do so I'll declare them private and provide no implementation:
class NonCopyable {
private:
NonCopyable( const NonCopyable& ); //not implemented anywhere
void operator=( const NonCopyable& ); //not implemented anywhere
};
Now I can select any return type for operator=() member function. Will it matter which return type I select?
No, the return type doesn't matter.†
The C++ standard does not impose any requirements on the return type for copy assignment special member functions that you declare yourself. It just needs to be an operator=() that acccepts "exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&".†† Therefore, void operator=( const NonCopyable& ); is still a copy assignment operator (a user-declared one, to be specific).
Because you have in fact supplied your own copy assignment operator, it will surpress the generation of the default copy assignment operator. This forces all calls to NonCopyable's copy assignment operator to resolve to yours, causing any attempts to use the copy assignment operator to fail to compile since it's declared private.
class Foo : NonCopyable
{
};
int main()
{
Foo a;
Foo b;
// Compiler complains about `operator=(const NonCopyable&)`
// not accessible or something like that.
a = b;
}
And since I'll never be able to actually use it, it doesn't matter that it's not exactly the canonical copy assignment operator. If I tried to use the copy assignment operator it would result in a compiler error, which is exactly what you want.
† Of course, it does matter, stylistically speaking, if the copy assignment operator actually does something. Generally you want your operators to behave just like the built-in ones, so returning a X& is good practice when you're actually doing assignment.
†† C++ Standard: 12.8 Copying class objects [class.copy]
9 A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one
parameter of type X, X&, const X&, volatile X& or const
volatile X&.
No, since you'll never call this operator in your code. I tend to keep the return type NonCopyable&, for clarity and consistency.
It matters a tiny, tiny bit:
void ensures a small percentage of accidental/misguided calls (a = b = c / f(d = e)) from within the class's implementation produce compile time errors rather than link time errors, which may save compile time and be more understandable (minimally relevant for large classes touched by many developers, some with limited prior familiarity).
void would ring an alarm bell for me (and hopefully most developers), wondering whether you:
wanted to remove the default-generated operator=
were just lazy about the extra typing, or
were unfamiliar/uncaring re the generally expected semantics of operator=.
With the open question in mind, other programmer are less likely to come along and think you just didn't get around to providing the implementation and add it casually (you may feel comments are adequate).
returning a reference to type may make the overall function signature more instantaneously recognisable, or visually searching past a complicated type to find operator= could have the opposite effect - all in the eye (and mind) of the beholder....
No, because you can return anything from operator=, even if you define its implementation.
No, it does not matter, since you never implement a return statement. If you try to invoke the operator, the compiler won't be able to find an implementation (with any return type, so the return type is irrelevant).
Interestingly, boost::noncopyable's copy assignment operator is declared to return a const noncopyable&, but I guess that's just convention.