Можно ли исключить исключение из тернарного оператора?

Иногда удобно или даже необходимо иметь функцию, которая имеет только один оператор (это необходимо при возврате constexpr). Если необходимо проверить условие и разрешить только один оператор, единственным условием является условный оператор. В случае ошибки было бы неплохо выбросить исключение из условного оператора, например:

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

Вышеуказанная функция не компилируется, однако, когда используется, например, как (живой пример):

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

Компилятор жалуется на попытку привязки ссылки const к временному. Компилятор, однако, не жалуется на throw -expression. Итак, вопрос: можно ли исключить исключения из условного оператора, и если да, то что происходит с вышеуказанным кодом?

Ответы

Ответ 1

Условный оператор описан в 5.16 [expr.cond]. В его пункте 2 содержится следующий текст:

Второй или третий операнд (но не оба) является выражением throw (15.1); результат будет другого типа и является prvalue.

Это говорит о том, что разрешено исключать исключение из условного оператора. Однако, даже если другая ветвь является lvalue, она превращается в rvalue! Таким образом, невозможно связать lvalue с результатом условного выражения. Помимо перезаписи условия с использованием оператора запятой, код можно переписать, чтобы получить только lvalue из результата условного оператора:

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

Несколько сложным делом является то, что возвращение ссылки const из функции будет компилироваться, но на самом деле возвращает ссылку на временную!

Ответ 2

Формулировка в стандарте составляет около 5.16/2:

Если либо второй, либо третий операнд имеет тип void, тогда стандартные преобразования конверсий lvalue-to-rvalue (4.1), array-to-pointer (4.2) и function-to-pointer (4.3) выполняются на второго и третьего операндов, и одно из следующих значений:

- второй или третий операнд (но не оба) является выражением throw (15.1); результат будет другого типа и является prvalue.

Это объясняет поведение, которое вы получаете. Легально бросать, но тип выражения является чисто-rvalue (даже если выражение является lvalue), и вы не можете таким образом связать не const const lvalue-reference

Ответ 3

Это можно сделать следующим образом:

return it == end? (throw std::runtime_error("no element"),*it): *it;