web-dev-qa-db-ja.com

三項演算子が左辺値を返すのはなぜですか?

長い間、三項演算子は常に右辺値を返すと思っていました。しかし、驚いたことに、そうではありません。次のコードでは、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 ;
}
39

ijはどちらもglvaluesです(詳細は この値カテゴリのリファレンス を参照) 。

次に、 この条件演算子の参照 を読むと、次のようになります。

4)E2とE3が同じ型と同じ値のカテゴリのglvalueである場合、結果は同じ型と値のカテゴリを持ちます

したがって、(i < 3) ? i : jの結果は、割り当て可能なglvalueになります。

しかし、そのようなことをすることは、私がお勧めすることではありません。

35

このルールの詳細は [expr.cond] にあります。タイプと値カテゴリのいくつかの組み合わせには、多くのブランチがあります。しかし、最終的には、式はデフォルトの場合のprvalueです。あなたの例のケースはパラグラフ5でカバーされています:

2番目と3番目のオペランドが同じ値カテゴリのglvalueであり、同じ型である場合、結果はその型と値カテゴリになり、2番目または3番目のオペランドがビットフィールドである場合、またはどちらもビットフィールドです。

変数名であるijは、どちらもint型の左辺値式です。したがって、条件演算子はint lvalueを生成します。

三項条件演算子は、2番目と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

ライブデモ

isTernaryAssignableに渡すオペランドは、減衰を受けないようにする必要があります(たとえば、ポインターに減衰する配列) )。

2
P.W