Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when returning reference to type given the expression: `cond ? *this : throw()`

This appears to be an error, but I just want to confirm. Is the following well formed? If not, why not?

#include <iostream>

struct X
{
    int value;
    constexpr X(int value) : value(value) {}

    constexpr X& do_something(int x)
    {
        return x < 3 ? *this : throw("FAIL");
        //return *this;
    }
};

int main()
{
    X x(2);
    std::cout << x.do_something(1).value << std::endl;
}

Under VC++2015 R3 with default solution switches, I get:

warning C4172: returning address of local variable or temporary

g++ (GCC) 5.4.0 with switches -Wall -pedantic I get:

error: invalid initialization of non-const reference of type ‘X&’ from an rvalue of type ‘X’
   return x < 3 ? *this : throw("FAIL");
                                      ^

However, clang version 3.9.1 (tags/RELEASE_391/final) with the switches -Wall -pedantic doesn't have a problem with it.

Using a return *this; of course doesn't have a problem.

like image 224
Adrian Avatar asked Mar 27 '17 19:03

Adrian


3 Answers

Since you have a C++14 tag, the code is 100% well-formed C++14.

Core issue 1560 removed the gratuitous lvalue-to-rvalue conversion here, and as a defect report resolution it should be eventually applied all the way back to the C++98/03 mode of compilers offering such a mode.

See also GCC bug 64372.

like image 67
T.C. Avatar answered Sep 30 '22 04:09

T.C.


C++11 15/2 says:

A try-block is a statement (Clause 6). A throw-expression is of type void...

Then 5.16/2: (from a question that @Fred Larson Mjollnir'd and then un-duped)

If either the second or the third operand has type void, then the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the second and third operands, and one of the following shall hold:

— The second or the third operand (but not both) is a throw-expression (15.1); the result is of the type of the other and is a prvalue.

— Both the second and the third operands have type void; the result is of type void and is a prvalue.

So from here we see that the conditional expression operator (ternary) portion is legal. But the result of the ?: is a prvalue which then cannot be legally bound to the non-const X& return type and is in fact ill-formed.

like image 39
Mark B Avatar answered Sep 30 '22 05:09

Mark B


Why not use something that's both sure to work and is easier to read instead? For example:

X& do_something(int x)
{
    if (x >= 3)
        throw("FAIL");

    return *this;
}

I'm not an expert regarding the compilers, but my guess is the example you posted will work if the compiler handles that particular corner case. The way I read this, your do_something(int x) function expands to this:

X& do_something(int x)
{
    if x < 3
        return *this;
    else
        return throw("FAIL");
}

Now, throw is a keyword and as such doesn't have a return value, so strictly speaking this is a compile-time error. However, I guess compilers (or at least some of them) are kind enough to go: "Oh, OK... There's no return value here, but throw is a special situation that'll cause the function to not return, anyway, so let's not complain and let the exception handling at runtime take care of this."

I personally don't like to take my chances with compilers and try to keep things as straight as possible, but... while that may arguably be a better practice, it may also be just a personal preference.

like image 45
Nickey Avatar answered Sep 30 '22 05:09

Nickey