2

I have the following program:

#include <iostream>
#define PRINT_LOCATION()\
 do { std::cout << __PRETTY_FUNCTION__ << "\n"; } while (false)
struct foo
{
 int val;
 foo()
 : val(1)
 {
 PRINT_LOCATION();
 }
 foo(const foo& other)
 : val(other.val * 2)
 {
 PRINT_LOCATION();
 }
 foo(foo&& other)
 : val(other.val * 2)
 {
 PRINT_LOCATION();
 }
};
int main()
{
 foo f{foo{foo{foo{}}}};
 std::cout << "value = " << f.val << "\n";
 if (f.val == 1)
 throw f;
}

Compilation and execution:

[mkc /tmp]$ g++ -Wall -Wextra -pedantic -std=c++14 -O0 -o a.out main.cpp
[mkc /tmp]$ ./a.out 
foo::foo()
value = 1
foo::foo(foo&&)
terminate called after throwing an instance of 'foo'
Aborted (core dumped)
[mkc /tmp]$ clang++ -Wall -Wextra -pedantic -std=c++14 -O0 -o a.out main.cpp
[mkc /tmp]$ ./a.out 
foo::foo()
foo::foo(foo &&)
foo::foo(foo &&)
value = 4
[mkc /tmp]$

I know that the compiler is allowed to remove some constructor calls, but isn't it only allowed to do it when there are no side effects? It looks like Clang is correct here, is it a bug in GCC?

Barry
312k32 gold badges736 silver badges1.1k bronze badges
asked Mar 10, 2017 at 21:09
1
  • It's never been the case that the compiler has to consider whether or not there are side effects. Commented Mar 10, 2017 at 21:30

2 Answers 2

3

In C++14, both compilers are correct. From [class.copy] in N4296, which I think is close to C++14:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [...] This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
— in a return statement in a function [...]
— in a throw-expression (5.17), [...]
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
— when the exception-declaration of an exception handler [...]

This declaration:

foo f{foo{foo{foo{}}}};

precisely meets that third criteria, so the compiler is allowed to, but isn't required to, elide that copy/move. Hence, both gcc and clang are correct. Note that if you do not want copy elision, you can add the flag -fno-elide-constructors.


In C++17 mode, there would not even be a move to elide. The initialization rules themselves in [dcl.init] change to read:

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. [ Example: T x = T(T(T())); calls the T default constructor to initialize x. —end example ]

answered Mar 10, 2017 at 21:34
Sign up to request clarification or add additional context in comments.

Comments

1

Neither is incorrect. This is called copy elision. As @chris pointed out below, it is only a required optimization in C++17. More details can be found on cppreference.com. The relevant section prior to C++17 is:

Under the following circumstances, the compilers are permitted to omit the copy- and move- (since C++11)constructors of class objects even if copy/move (since C++11) constructor and the destructor have observable side-effects.

When a nameless temporary, not bound to any references, would be moved or (since C++11) copied into an object of the same type (ignoring top-level cv-qualification), the copy/move (since C++11) is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be moved or (since C++11) copied to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".

answered Mar 10, 2017 at 21:16

5 Comments

Clang is not incorrect. That text has a (since C++17) beside it because C++17 introduces guaranteed copy elision. Note that a) The OP is compiling with C++14; and b) This is a new feature that has not been supported for very long on either compiler (possibly GCC 7 and Clang 4.0, going from memory). Before C++17, copy elision can happen, it's just not guaranteed.
Ahh that explains why I was confused. I thought it was optional and I didn't see the C++17 tag next to that excerpt.
@chris Updated my answer based on your comments. Thanks
Is there a way to tell GCC not to perform this optimization?
@mkcms I think this answer might be able to do it, but it's probably not a good idea for production code.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.