三元运算符为啥以及何时返回左值?

Posted

技术标签:

【中文标题】三元运算符为啥以及何时返回左值?【英文标题】:Why and when does the ternary operator return an lvalue?三元运算符为什么以及何时返回左值? 【发布时间】:2019-07-08 08:46:14 【问题描述】:

长期以来,我一直认为三元运算符总是返回一个右值。但令我惊讶的是,事实并非如此。在下面的代码中,我看不出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 ;

【问题讨论】:

附带说明,如果您将int foo() 更改为int &amp;foo(),那么foo()=10; 也可以使用。 “三元运算符总是返回一个右值。令人惊讶的是它确实” 你的意思是“不”,因为它可以返回左值(当双方都是左值时)。 另见***.com/questions/1082655/…。如那里所述,您始终可以使用*((i&lt;3) ? &amp;i : &amp;j) = 7;,因此 C++ 规则可以概括为“即使没有*&amp;,它仍然有效” 在 C 中,条件运算符永远不会产生左值。在 C++ 中,有时会这样。这是人们应该避免提及“C/C++”的一个例子。像这样的各种细微差别可能最终与任何特定问题相关。 【参考方案1】:

此规则在[expr.cond] 中有详细说明。对于类型和值类别的几种组合,有许多分支。但最终,表达式在默认情况下是纯右值。第 5 段涵盖了您示例中的情况:

如果第二个和第三个操作数是相同值的glvalues 类别并具有相同的类型,结果是该类型和值 类别,如果第二个或第三个操作数是一个位域 位域,或者如果两者都是位域。

ij 都是变量名,都是int 类型的左值表达式。所以条件运算符产生一个int左值。

【讨论】:

【参考方案2】:

ij 都是 glvalues(有关详细信息,请参阅 this value category reference)。

那么,如果您阅读this conditional operator reference,我们就到了这一点:

4) 如果E2和E3是相同类型和相同值类别的glvalues,则结果具有相同的类型和值类别

所以(i &lt; 3) ? i : j的结果是一个glvalue,可以赋值给。

但是,我真的不建议这样做。

【讨论】:

@SoulimaneMammar 问题在于常量,而不是值类别。您不能将带有已删除 operator= 的对象放在分配的左侧;这并不意味着它不是左值。 @Someprogrammerdude - 你的第一条评论代表了左值概念的起源:可以在赋值左侧的东西。虽然概念已经超越了这一点,但名称仍然存在。 @SoulimaneMammar 数组(例如字符串文字)是棘手的左值。当使用它们的值时,它们会隐式转换为指向第一个元素的指针(这称为衰减),并且衰减的指针不是左值。这就是错误诊断的原因。更一般地说,即使数组不是 const,也不能在语言中分配数组。 @SoulimaneMammar - 哪个编译器? gnu c++ 对"Hello" = "World" 的诊断是error: assignment of read-only location '"Hello"',而LLVM 的诊断是error: read-only variable is not assignable 主要用例是引用的初始化:int &amp;i = b ? i1 : i2;【参考方案3】:

三元条件运算符将产生一个左值,如果它的第二个和第三个操作数的类型是左值。

您可以使用函数模板is_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 的操作数将不会衰减(例如衰减为指针的数组)。

【讨论】:

以上是关于三元运算符为啥以及何时返回左值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Swift 零合并三元运算符不返回未包装类型?

为啥带赋值的三元运算符不返回预期的输出?

即使在三元运算符中给出的左值很好,赋值语句也会出错

为啥在三元运算符中使用“0”会返回第一个值?

为啥使用三元运算符返回字符串与在等效 if/else 块中返回的代码有很大不同?

为啥以及何时使用重组分支?