C ++: rvalue references used in ternary operator seem to break existing code

I am currently porting one of my projects, which was developed for years using Borland C ++ Builder 5 and 6, until the most recent update of Embarcadero C ++ Builder XE 3. 2. XE 3 supports some new C ++ 11-things like rvalue references, which of course are completely new to me, due to the former using very old compilers. It only took me a few modifications to get my project to compile, but I ran into one issue at runtime, which appears to be the result of new rvalue references and moving semantics.

I have a class with a field of type std :: wstring storing a path that is only read from one method using that field in a ternary operator, e.g .:

std::wstring retVal = someCondition ? this->classField : Util::doSomething(someArg);

      

someCondition is just a call to std :: wstring.empty () and Util :: doSomething returns std :: wstring as a value, with no reference or other related, just copied data.

With XE 3, after the first request of someCondition to true, retVal is correctly populated with the contents of the Field class, but after that the contents of the Field class are empty. Using the debugger, I could do the execution for the optimized rvalue referenced assignment statement:

 #if _HAS_RVALUE_REFERENCES
[...]
    _Myt& operator=(_Myt&& _Right)
        {   // assign by moving _Right
        return (assign(_STD forward<_Myt>(_Right)));
        }

    _Myt& assign(_Myt&& _Right)
        {   // assign by moving _Right
[...]

      

What I read about rvalue references explains my problem perfectly, I even found two comments explaining why my Field class is treated as an rvalue.

stack overflow

stack overflow

I can fix the line above with one extra manual copy of the Class:

std::wstring retVal = someCondition ? std::wstring(this->classField) : Util::doSomething(someArg);

      

But this does not solve the problem that I need to reconsider every use of the ternary operator (where lvalues ​​and rvalues ​​are mixed) in every project I want to pass without any help from the compiler, as this is a runtime problem that can happen or not to happen.

What I don't understand is if there is something wrong or bad in the practice of using ternary operator? Are there any better solutions for detecting these cases? Why do I need to manually copy some objects to optimize optimization? Is this really intended behavior and no problem in most of your codebases? How do you deal with such problems?

I am a little confused on how to proceed right now and would greatly appreciate any suggestions and / or explanations. Thank!


I've tested the following two cases that worked:

http://ideone.com/mWxxK3

The first example is similar to my case, expect no class instances are used to store the global string. dummy2 is correctly populated and the dummy stores its contents.

std::wstring retVal = someCondition ? this->classField : doSomethingResult;

      

When I change my line above to remove the rvalue while storing the Util: ... result in std :: wstring before using the ternary operator, everything works as expected. retVal has its content, and this-> classField stores it too.

What's the conclusion now?: - /

+3


source to share


1 answer


Looks like a compiler error. The relevant section of the standard is 5.16p6:

[If] The second and third operands are of the same type; result of this type. If the operands are of class type, the result is a temporary prvalue for the result type, which is initialized by copying from the second operand or the third operand depending on the value of the first operand.


If Util::doSomething(someArg)

returned std::string&&

instead of a value, we need section 5.16p3:



Otherwise, if the second and third operands are of different types and either have the (possibly cv-qualified) class type, or both glvalues ​​of the same value category and the same type other than cv-qualified, an attempt is made to converting each of these operands to the type of the other. The process of determining whether a E1

type operand expression can be converted T1

to match a E2

type operand expression is T2

as follows:

  • If E2

    is an lvalue: E1

    can be converted to match E2

    if E1

    can be implicitly converted to an "lvalue reference to T2

    " type, subject to the constraint that, when converted, the reference must bind directly to the lvalue.
  • If E2

    is the value x: E1

    can be converted to match E2

    if E1

    can be implicitly converted to type "rvalue reference to T2

    ", subject to the constraint that the reference must directly bind.
  • If E2

    is an rvalue or if none of the above conversions can be performed and at least one of the operands is (possibly cv-qualit) of the class type:
    • If E1

      u E2

      have a class type and the base types of the classes are the same or one is the base class of the other: E1

      can be converted to match E2

      if the class T2

      is the same type as the base class of the class T1

      and cv-qualification T2

      is the same cv-qualification as or higher cv-qualification than cv-qualification T1

      . If a conversion is applied, it is E1

      changed to the prvalue of type T2

      by copying-initializing the temporary type T2

      from E1

      and using that temporary as the converted operand.
    • Otherwise (that is, if E1

      or E2

      is of a non-classical type, or if both types are of class types, but the base classes are not the same, or one is the base class of the other): E1

      can be converted to match E2

      if E1

      can be implicitly converted to a type that will have an expression E2

      if they E2

      were converted to a prvalue (or the type it has if it E2

      is a prvalue).

The fourth mark applies, so a copy should be made automatically, without any changes required for your source code.


In both cases, the temporary copy can be moved safely at build retval

.

+4


source







All Articles