Можно ли исключить исключение из тернарного оператора?
Иногда удобно или даже необходимо иметь функцию, которая имеет только один оператор (это необходимо при возврате 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;