Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is constructed string("Plain Old C chain") a rvalue?

Tags:

c++

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?
like image 593
NGI Avatar asked Feb 06 '17 11:02

NGI


2 Answers

  • 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.

like image 122
Jonathan Wakely Avatar answered Nov 08 '22 21:11

Jonathan Wakely


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)

like image 8
Vittorio Romeo Avatar answered Nov 08 '22 22:11

Vittorio Romeo