Почему и когда троичный оператор возвращает lvalue?
Долгое время я думал, что троичный оператор всегда возвращает значение. Но, к моему удивлению, это не так. В следующем коде я не вижу разницы между возвращаемым значением foo
и возвращаемым значением троичного оператора.
#include <iostream>
int g = 20 ;
int foo()
{
return g ;
}
int main()
{
int i= 2,j =10 ;
foo()=10 ; // not Ok
((i < 3) ? i : j) = 7; //Ok
std::cout << i <<","<<j << "," <<g << std::endl ;
}
Ответы
Ответ 1
И i
и j
являются значениями glvalues (подробности см. В этой категории значений).
Тогда, если вы прочитаете эту ссылку на условный оператор, мы подойдем к следующему:
4) Если E2 и E3 являются glvalues одного и того же типа и той же категории значения, то результат имеет тот же тип и категорию значения
Таким образом, результат (i < 3)? i: j
(i < 3)? i: j
- это glvalue, которому можно присвоить.
Однако делать что-то подобное на самом деле я бы не рекомендовал.
Ответ 2
Правила этого подробно описаны в [expr.cond]. Существует множество веток для нескольких комбинаций типов и категорий значений. Но в конечном счете, выражение является prvalue в случае по умолчанию. Случай в вашем примере охватывается параграфом 5:
Если второй и третий операнды являются glvalues одной и той же категории значений и имеют один и тот же тип, результат имеет тип и категорию значений, и это битовое поле, если второй или третий операнд является битовым полем, или если оба являются битовыми полями.
И i
и j
, будучи именами переменных, являются lvalue-выражениями типа int
. Таким образом, условный оператор создает int
lvalue.
Ответ 3
Тернарный условный оператор выдаст lvalue, если тип его второго и третьего операндов - lvalue.
Вы можете использовать шаблон функции is_lvalue
(ниже), чтобы выяснить, является ли операнд lvalue, и использовать его в шаблоне функции isTernaryAssignable
чтобы узнать, можно ли ему присвоить.
Минимальный пример:
#include <iostream>
#include <type_traits>
template <typename T>
constexpr bool is_lvalue(T&&) {
return std::is_lvalue_reference<T>{};
}
template <typename T, typename U>
bool isTernaryAssignable(T&& t, U&& u)
{
return is_lvalue(std::forward<T>(t)) && is_lvalue(std::forward<U>(u));
}
int main(){
int i= 2,j =10 ;
((i < 3) ? i : j) = 7; //Ok
std::cout << std::boolalpha << isTernaryAssignable(i, j); std::cout << '\n';
std::cout << std::boolalpha << isTernaryAssignable(i, 10); std::cout << '\n';
std::cout << std::boolalpha << isTernaryAssignable(2, j); std::cout << '\n';
std::cout << std::boolalpha << isTernaryAssignable(2, 10); std::cout << '\n';
}
Выход:
true
false
false
false
LIVE DEMO
Примечание: операнды, которые вы передаете isTernaryAssignable
, должны быть такими, чтобы они не подвергались распаду (например, массив, который распадается на указатель).