Namespaces
Variants
Views
Actions

Talk:cpp/language/copy elision

From cppreference.com
< Talk:cpp‎ | language
Revision as of 09:29, 18 November 2019 by T. Canens (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Maybe link to the related treat an lvalue as an rvalue for overload resolution concept that occurs in return and throw statements? Or mention it? These two rules (copy elision and the one mentioned above) are intertwined.

dyp --84.162.22.17 11:26, 20 July 2015 (PDT)

sure, cpp/language/return#Notes links here, it makes sense for this page to link back (perhaps as another paragraph under Notes). --Cubbi (talk) 11:29, 20 July 2015 (PDT)

Summary of examples

// constructor A called once
// because a default destructor and constructor can be made
class A
{
public:
	template<typename T>
	A(T&& t)
	{
	}
};

// constructor B called twice because default constructor can't
// be made due to the destructor
class B
{
public:
	template<typename T>
	B(T&& t)
	{
	}
	~B() = default;
};

// constructor C called once
// because the other one is declared (does not have to be defined)
class C
{
public:
	C(const Test& t);
	template<typename T>
	C(T&& t)
	{
	}
};


// can't construct at all
// the constructor must be available even if it isn't used due to elision
class D
{
public:
	D(const Test& t) = delete;
	template<typename T>
	D(T&& t)
	{
	}
};

// constructor called once
// the move constructor function can be elided
class E 
{
public:
	E(Test&& t);
	template<typename T>
	E(T&& t)
	{
	}
};

// the explicit constructor can't be used and so can't be elided
// so the other constructor is called twice
class F 
{
public:
	explicit F(Test&& t);
	template<typename T>
	F(T&& t)
	{
	}
};

// move not allowed so will not compile
class G 
{
public:
	G(Test&& t) = delete;
	template<typename T>
	G(T&& t)
	{
	}
};

QuentinUK (talk) 05:41, 23 February 2016 (PST)

Contents

[edit] Muddling of copy/move of temporaries and construction of objects by prvalues

This article unfortunately muddles the water of construction of objects by prvalues and that of copying other temporary objects and the elision of that copy.

"optimizes out copy- and move- (since C++11)constructors, resulting in zero-copy pass-by-value semantics." and goes on to explain how this is (supposedly) mandatory in C++17 by showing

  T t = T(T(T()))

However, nothing in C++17 says that there are temporaries involved in this, not even conceptually. From the perspective of the Standard, "t" is being value-initialized, and that is it. The article makes it really confusing to grasp the C++17 semantics, muddling it with copy elision that can take place when returning named local objects (NRVO).

I propose to make a notice "C++17 changes the semantics of prvalues. All of the following only applies to pre-C++17", or something similar. 87.143.171.180 05:32, 25 May 2017 (PDT)

The only mentions of temporaries on this page are in the "until C++17" revbox. If anything, this page should bring the "since C++17" revbox down to be next to the "until C++17" revbox. --Cubbi (talk) 06:52, 25 May 2017 (PDT)
You might aswell call the following a `new-elision` because since C++98, the compiler is required to "elide away" the `*new int(0)` that it would otherwise employ.
int a = 0;
87.143.171.180 05:46, 25 May 2017 (PDT)
there is no new-expression in this example, new-elision does not apply. --Cubbi (talk) 06:52, 25 May 2017 (PDT)
Right. And in "T t = T(T(T()))" there is no copy, copy-elision does not apply 87.143.171.180 14:22, 25 May 2017 (PDT)
Another point: Teaching this in terms of "copy-elision is required since C++17" can cause a lot of confusion, because in constant expression contexts, copy elision is also required. Yet, since this is a copy-elision, the compiler still checks for usable copy/move constructors. So the following code is ill-formed:
struct A {
    constexpr A(int a):a(a) { }
    constexpr explicit A(const A& a):a(a.a) { }  
    int a;
};

constexpr A g() { A a(0); return a; }
constexpr A a = g();

87.143.171.180 06:12, 25 May 2017 (PDT)

Only one of the four kinds of copy-elision was made mandatory in C++17. All four are required in constant expressions. What is confusing? --Cubbi (talk) 06:52, 25 May 2017 (PDT)
You say that in both instances (first instance: one of four kinds in C++17, second instance: all four kinds within constant-expressions). Yet one instance requires an accessible copy/move constructor and the other doesn't (hint: that is because one of them is not copy-elision) 87.143.171.180 14:22, 25 May 2017 (PDT)
The paper that removed the requirement for copy/move constructors in that case where copies from rvalues are elided was actually called "Guaranteed copy elision". --Cubbi (talk) 16:16, 25 May 2017 (PDT)
I think there's a confusion here. It was called that way because the goal was to have no copies/moves for those cases. And the way that it worked in C++14 was copy-elision, so the most obvious way to achieve the goal is to guarantee the copy-elision. But the implementation chosen by the paper to achieve that goal is (quote): "The approach described herein achieves this not by eliding a copy, but by reworking the definition of the value categories (glvalue versus prvalue) such that it is unnatural to perform the copy in the first place.". See this Stackoverflow answer for an explanation: https://stackoverflow.com/a/38043447 . Therefore there's an important difference between a "guaranteed copy elision" in constexpr contexts and the no-copy semantics of initialization-by-prvalues. 87.143.167.148 14:49, 26 May 2017 (PDT)
I'm fairly sure Cubbi knows the standardese here. This is getting to a level of pedantry that even I am not comfortable with. I vaguely recall reading somewhere that the mandatory copy elision in constant expressions is not implementable - I think from Richard Smith, even - but I can't find it right now.
Regardless, while the language-lawyer part of me have some sympathy for your argument, I strongly suspect that "guaranteed elision" terminology is far easier to grasp for non-language-lawyers familiar with C++. In some aspects, it's even more correct (reflective of the intent and the implementations), since the current wording incorrectly forces materialization of a temporary in some situations where it would have been elided under previous rules.
More generally, the committee cares only about the present incarnation of the standard and can revamp stuff as much as they like, but we need to describe multiple revisions of the language, preferably in a coherent manner, and that sometimes means that we have to describe new things using old terminology. T. Canens (talk) 15:53, 26 May 2017 (PDT)
Thanks for commenting. My motivation was not to spread pedantry, I'm sorry if it read like that. The argument "we need to describe multiple revisions of the language, preferably in a coherent manner, and that sometimes means that we have to describe new things using old terminology" is very good.
I am unsure about what is easier to grasp for a non-lawyer: Saying "T(...)" specifies the initialization of a "T" object that may be provided elsewhere, or saying "T(...)" creates a temporary object, which is however required to be elided by folding it with the creation of an object elsewhere (copy-elision+no-requirement-of-accessibility) (note that this is not even correct for C++14-downwards, since non-class/non-array cases do not create a temporary object, so it's even more complicated to teach). I *do* agree that it is easier to explain in a setting where you need to cover all C++ versions though. My focus was that of a learner of C++17, not that of a wiki-writer for all versions of C++, so I want to apologize. 87.143.166.149 05:21, 27 May 2017 (PDT)
For "T(...)", we say in cpp/language/explicit_cast "this expression is an prvalue of type new_type, designating a temporary(until C++17)whose result object is(since C++17) of that type". I hope there are no leftover temporaries here in the C++17 narrative (except where materialized) --Cubbi (talk) 08:25, 27 May 2017 (PDT)

[edit] CWG 2022

I'm feeling less and less comfortable about including CWG 2022 (guaranteed copy elision in constant expressions) without mentioning CWG 2278, in which IIUC Richard Smith argues that 2022 is unimplementable and suggests completely reversing the direction. (That the only public indication of CWG 2278's existence is buried in https://wg21.link/index.json doesn't help, either.) Cubbi, can you check if there are any notes on the wiki about this? T. Canens (talk) 22:01, 25 July 2017 (PDT)

the entry pointed to by wg21.link/cwg2278 just says "[Detailed description pending.]" and links to the reflector post.. which does seem convincing. Quoting:
it is mathematically impossible to guarantee NRVO in all such cases. Consider:
struct B { B *self = this; }; 
extern const B b; 
constexpr B f() { 
  B b; 
  if (&b == &::b) return B(); 
  else return b; 
} 
constexpr B b = f(); // is b.self == &b?
Here, under CWG2022 an implementation is required to perform NRVO *if and only if* it does not perform NRVO. Oops. I think we should completely reverse direction here, and instead guarantee that NRVO is *not* performed in constant expression evaluation.
I made a small note on the page --Cubbi (talk) 06:20, 26 July 2017 (PDT)
Ugh. CWG really needs a Daniel clone. The note LGTM. T. Canens (talk) 12:33, 26 July 2017 (PDT)

[edit] Mandatory return value optimization

The page says "Return value optimization is mandatory and no longer considered as copy elision; see above". The way it's presented seems to indicate that all kinds of RVO are mandatory, but it's probably only meant to apply to constexpr RVO. Can it be presented differently to be more clear? 198.74.35.10 12:30, 3 October 2019 (PDT)

that particular line in a "since C++17" box refers to the contents of the "until C++17" box directly above it. It is there because otherwise the text looks like RVO existed until C++17 and then gone. (nothing to do with constexpr, either way) --Cubbi (talk) 06:03, 4 October 2019 (PDT)
In that case I'm having trouble seeing where the ISO docs mandate RVO. 66.10.131.106 23:34, 27 October 2019 (PDT)
that can be found in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0135r1.html --Cubbi (talk) 14:04, 29 October 2019 (PDT)

[edit] Wording of C++17 "mandatory elision" is logically inconsistent

It first says this is an omission of copy/move, and then says there's conceptually no copy/move.

This wording is twisted, logically contradicts itself, and hard to understand.

What do you think, T. Canens? What do you think of this problem? And what problem do you think my version has so that you undid it?

Cooper2222 (talk) 22:43, 17 November 2019 (PST)

I feel helping to improve this site is so hard. I can stop if you feel that's the better way.

Cooper2222 (talk) 01:15, 18 November 2019 (PST)

C++17's whole "unmaterialized prvalue" thing is a specification abstraction for what is really "guaranteed elision"; we are still seeing the fallout as the abstraction leaks are discovered and plugged (a temporary is not materialized...except when it is; there's no temporary at all...but the destructor is potentially invoked). I removed the "conceptually no copy/move"; that part isn't critical. If that's your only complaint, I certainly don't see the point for the major surgery you've been doing. T. Canens (talk) 09:29, 18 November 2019 (PST)