13

I was wondering if a copy elision is made in the call of foo(string) below. (Note: foo(string) belongs to an interface that I can not change).

For this I attempted to check whether constructed string("Hello world!") is a rvalue.

I searched on SO how to do this programmatically and found this post: How to determine programmatically if an expression is rvalue or lvalue in C++?

void foo( string str)
{
    cout << str << endl;
}

int main()
{
    foo("Hello world!");
    cout << is_rvalue_reference<decltype(string("Hello world!"))>::value  << endl;
}

result is

Hello world!
0

I thought I would get true to is_rvalue_reference< xxx >::value

  • Where am I wrong?
  • string("Hello world!") may be a rvalue but does not seem to be a "reference of any kind" (either lvalue, rvalue, universal ... ) so that I got false result. Is there a way to get a true answer in case of a rvalue?
  • Is there copy elision or not in his example?
Community
  • 1
  • 1
NGI
  • 852
  • 1
  • 12
  • 31

2 Answers2

21
  • Where am I wrong?

std::string("") is an rvalue, but decltype(std::string("")) is not an rvalue reference. The type of a std::string object is ... std::string, of course.

You have a category error. An rvalue is a kind of expression, an rvalue reference is a kind of type.

A temporary string object is an rvalue. The type string&& is an rvalue reference type.

Your decltype expression is not useful for what you're trying to do. Consider:

std::string s;
using type1 = decltype(s);
using type2 = decltype(std::string(""));
static_assert(std::is_same<type1, type2>::value, "same");

In both these cases decltype gives the same type: std::string. That's because decltype tells you about types, not value categories (i.e. whether an expression is an rvalue or an lvalue).

If you want to know whether an expression is an rvalue or an lvalue you need to know more than just its type. In the case of decltype(std::string("")) you are creating an unnamed temporary, which is an rvalue. You don't need to ask its type to know that.

  • string("Hello world!") may be a rvalue but does not seem to be a "reference of any kind" (either lvalue, rvalue, universal ... ) so that I got false result. Is there a way to get a true answer in case of a rvalue ?

What do you mean by "true" answer? Do you just mean a type trait that will give the result true?

You can ask if the type is convertible to an rvalue reference:

std::is_convertible<decltype(std::string("")), std::string&&>::value

That will tell you if you can bind an rvalue reference to the object. But it's a silly question: of course you can bind an rvalue reference of type X&& to a temporary of type X. You would never need to ask that.

And anyway, you function doesn't take an argument of type string&& so asking that question doesn't even tell you anything about your call to foo(std::string).

  • Is there copy elision or not in his example ?

Yes, initializing the function argument from a temporary string should not make any copies or moves, they should be elided. The C++14 standard says in [class.copy] p31 that a copy/move can be elided when a temporary object (that has not been bound to a reference) would be copied/moved to a class object of the same type. That condition is met when initializing a function argument of class type from a temporary of the same type. A compiler that doesn't perform that elision (at least when optimisations are enabled, or in "release" builds) is a bad compiler.

There is an explanation of the copy elision rules at http://en.cppreference.com/w/cpp/language/copy_elision -- see the part about a nameless temporary.

T.C.
  • 133,968
  • 17
  • 288
  • 421
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 1
    Could you add a standard quote for the *copy elision* paragraph? I cannot find a suitable one in [class.copy.elision]. – Vittorio Romeo Feb 06 '17 at 11:56
  • _"Yes, initializing the function argument from a temporary string should not make any copies or moves, they should be elided."_ Only as of the as-yet-unpublished C++17, no? Sure in practice we'd expect elision but that's not the same thing as implying a standard mandate with the term "should". – Lightness Races in Orbit Feb 06 '17 at 12:00
  • 2
    @LightnessRacesinOrbit, [no](http://coliru.stacked-crooked.com/a/7712d5169881a3e1). The standard says when elision is allowed (and it's not allowed elsewhere, or that would be a non-conforming implementation). A compiler **should** elide wherever possible. Not all compilers do that. GCC, Clang and Intel all elide for the linked example. – Jonathan Wakely Feb 06 '17 at 12:03
  • @JonathanWakely: Allowed to != Required to. And I'm not sure why you provided an example of elision in action; I already said we would expect it to occur in practice. – Lightness Races in Orbit Feb 06 '17 at 12:05
  • 1
    Which is why I said "should not". Not "will not". I meant exactly what I wrote. – Jonathan Wakely Feb 06 '17 at 12:05
  • I'm suggesting that readers could misread your "should" for something else, because in English it carries the sense of a _requirement_. I'm trying to help you get your point across as clearly as possible. – Lightness Races in Orbit Feb 06 '17 at 12:06
  • 1
    Then write your own answer. The question was "Is there copy elision or not" and my coliru demo shows there **is**. I said there **should be** elision, which is consistent with accepted specification writing to mean something to be expected, but not required. The word "should" does **not** imply something mandated by the standard, it has a specific meaning in ISO (and other) standards. – Jonathan Wakely Feb 06 '17 at 12:10
  • 1
    @VittorioRomeo, C++14 12.8 [class.copy] p31 third bullet. – Jonathan Wakely Feb 06 '17 at 12:14
  • @LightnessRacesinOrbit, since copy elision is never required in C++14, it's redundant to say "there is elision here, except maybe not depending on your compiler". That's implied in any discussion of copy elision prior to C++17. Copy elision in C++ today means something that is allowed but not required **by definition**. – Jonathan Wakely Feb 06 '17 at 12:20
  • 1
    @VittorioRomeo, also C++14 5.2.2 [expr.call] p4 "During the initialization of a parameter, an implementation may avoid the construction of extra temporaries [...]". Don't look in the C++17 draft for copy elision rules in C++ today, because in C++17 there is no copy elision at all for this example. The prvalue initializes the function argument, with no temporary involved so nothing that needs to be elided. – Jonathan Wakely Feb 06 '17 at 12:38
  • @JonathanWakely: _"Copy elision in C++ today means something that is allowed but not required by definition."_ Exactly my point. We are teaching here, so it would be best to be as clear as possible about that. – Lightness Races in Orbit Feb 06 '17 at 12:44
  • @JonathanWakely: Thanks - I edited your answer with the quotes from the standard you mentioned. – Vittorio Romeo Feb 06 '17 at 13:12
  • In everyday English "should" implies a strong recommendation, but not a strict requirement. "must" is a strict requirement. However, technical documentation often uses "should" or "shall" to mean a strict requirement. – M.M Feb 06 '17 at 13:14
  • 2
    @M.M I think in ISO standards only "shall" means a strict requirement, not "should". – R. Martinho Fernandes Feb 06 '17 at 13:29
  • @VittorioRomeo please don't add large chunks of copyrighted text to my answer. – Jonathan Wakely Feb 06 '17 at 13:44
  • @M.M I'm not aware of any technical documentation style that uses "should" as a strict requirement, see e.g. the ISO drafting guidelines, or [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt), or http://reqexperts.com/blog/2012/10/using-the-correct-terms-shall-will-should/ or http://asq.org/standards-shall-should or http://etherealmind.com/requirements-terminology/ etc. etc. – Jonathan Wakely Feb 06 '17 at 13:50
  • Am I missing something? I thought a `std::string` object was of type `std::string`, not `std:string`. – NoOneIsHere Feb 06 '17 at 17:57
  • @SeeOneRhino it was just a typo, obviously. – Jonathan Wakely Feb 07 '17 at 09:40
8

Where am I wrong?

An rvalue reference has the following form: T&&.

decltype(string("")) evaluates to string, not string&& - therefore it is not an rvalue reference.

is_same<decltype(string("Hello world!")), string>::value // true

string("Hello world!") may be a rvalue but does not seem to be a "reference of any kind"

string("Hello world!") is an expression with rvalue value category - more specifically it is a prvalue. Expressions are non-references. (More information: "In C++, what expressions yield a reference type when decltype is applied to them?".)


Is there a way to get a true answer in case of a rvalue ?

You can use a transformation that:

  • Given an lvalue, returns an lvalue reference.

  • Given an rvalue, returns an rvalue reference.

The behavior mentioned above can be obtained thanks to template argument deduction rules:

template <typename T>
using is_rvalue = std::is_rvalue_reference<T&&>;

is_rvalue<decltype(string(""))>::value // true
is_rvalue<decltype(std::declval<string&&>())>::value // true
is_rvalue<decltype(std::declval<string&>())>::value // false

The code above works because:

  • T&& is deduced as T&& both for xvalues and prvalues.

  • T&& is deduced as T& otherwise (i.e. for lvalues).


Is there copy elision or not in his example ?

In C++14, the compiler is allowed to (but not required) to elide the copies in your example.

(Credits to Jonathan Wakely for identifying the relevant C++14 standard draft quotes cited below.)

Relevant C++14 standard draft (N4296) quotes:

  • §12.8 [class.copy] p.31

    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. 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. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

    [...]

    [p.31.3] when a temporary class object that has not been bound to a reference 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

  • §5.2.2 [expr.call] p.4

    When a function is called, each parameter shall be initialized with its corresponding argument. [...] During the initialization of a parameter, an implementation may avoid the construction of extra temporaries by combining the conversions on the associated argument and/or the construction of temporaries with the initialization of the parameter. [...]

ISO/IEC. (2014). ISO International Standard ISO/IEC 14882:2014(E) – Programming Language C++. [Working draft]. Geneva, Switzerland: International Organization for Standardization (ISO). (Retrieved from https://isocpp.org/std/the-standard)

Community
  • 1
  • 1
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    _"That is already in the standard and called std::forward"_ but then your example doesn't use std::forward – Lightness Races in Orbit Feb 06 '17 at 12:01
  • 2
    I think it is a bit misleading to just say "it is not a reference". Expressions have a type (never a reference type) and a value category. – M.M Feb 06 '17 at 13:07
  • @LightnessRacesinOrbit: whoops, I forgot to change that when I changed my approach *(I was using `std::forward` before)*. @M.M: hopefully it is more accurate now. – Vittorio Romeo Feb 06 '17 at 13:23