Question

Sometimes it is convenient or even necessary to have a function which just one statement (it is necessary when returning a constexpr). If a condition needs to be checked and only one statement is allowed, the conditional operator is the only option. In case of an error it would be nice to throw an exception from the conditional operator, e.g.:

template <typename It>
typename std::iterator_traits<It>::reference
access(It it, It end) {
    return it == end? throw std::runtime_error("no element"): *it;
}

The above function doesn't compile, however, when used for example as (live example):

std::vector<int> v;
access(v.begin(), v.end());

The compiler complains about trying to bind a non-const reference to a temporary. The compiler doesn't complain about the throw-expression per se, though. So the question: Can exceptions be thrown from the conditional operator and, if so, what's going wrong with the above code?

Was it helpful?

Solution

The conditional operator is described in 5.16 [expr.cond]. Its paragraph 2 includes the following text:

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.

That says that it is allowed to throw an exception from the conditional operator. However, even if the other branch is an lvalue, it is turned into an rvalue! Thus, it isn't possible to bind an lvalue to the result of a conditional expression. Aside from rewriting the condition using the comma operator, the code could be rewritten to only obtain the lvalue from the result of the conditional operator:

template <typename It>
typename std::iterator_traits<It>::reference
access(It it, It end) {
    return *(it == end? throw std::runtime_error("no element"): it);
}

The somewhat tricky business is that returning a const reference from the function would compile but actually return a reference to a temporary!

OTHER TIPS

The wording in the standard is around 5.16/2:

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.

Which explains the behavior you are getting. It is legal to throw, but the type of the expression is a pure-rvalue (even if the expression is an lvalue) and you cannot thus bind a non-const lvalue-reference

It can be done like this:

return it == end? (throw std::runtime_error("no element"),*it): *it;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top