如何用C++函数实现类似三目运算符的功能

问题引出

今天aikilis问了我二个问题:
1 下面这段代码合法吗?

( i > 0 ? i : j ) = 1;

2 如何用一个原型为quest(bool,type,type)的函数实现三目符的功能?

经试验,第一个问题的答案是肯定的,虽然原来从没这么用过。
第二个问题确实费了很多脑筋。

三目符的性质

void test0() {
        int i = 0, j = 0, k;
        ( i > 0 ? i : j ) = 3;  // ok
        ( i > 0 ? i : 2 ) = 3;  // error
        ( i > 0 ? 1 : j ) = 3;  // error
        ( i > 0 ? 1 : 2 ) = 3;  // error
        k = ( j > 0 ? i : j );  // ok
        k = ( j > 0 ? i : 2 );  // ok
        k = ( i > 0 ? 1 : j );  // ok
        k = ( i > 0 ? 1 : 2 );  // ok
}

即在赋值右侧的时候,三目符返回左值右值都可以。
但是放在赋值左侧的时候,三目符返回值必须是左值。

所以我们的目标是实现如下代码:

void test1() {
        int i = 0, j = 0, k;
        quest( i > 0, i, j ) = 3;  // ok
        quest( i > 0, i, 2 ) = 3;  // error
        quest( i > 0, 1, j ) = 3;  // error
        quest( i > 0, 1, 2 ) = 3;  // error
        k = quest( j > 0, i, j );  // ok
        k = quest( j > 0, i, 2 );  // ok
        k = quest( i > 0, 1, j );  // ok
        k = quest( i > 0, 1, 2 );  // ok
}

失败的尝试

1 最开始的想法,通过函数重载,输入是左值就返回左值引用,输入是右值就返回右值。

// 为左值准备的
template< typename T >
T&  quest( bool cond, T& true_val, T& false_val ) {
    if( cond ) return true_val;
    return false_val;
}
// 为右值准备的
template< typename T >
T  quest( bool cond, const T true_val, const T false_val ) {
    if( cond ) return true_val;
    return false_val;
}

然而这是不行的。编译结果:

void test1() {
        int i = 0, j = 0, k;
        quest( i > 0, i, j ) = 3;  // ambiguous,因为左值也可以当右值用
        quest( i > 0, i, 2 ) = 3;  // error
        quest( i > 0, 1, j ) = 3;  // error
        quest( i > 0, 1, 2 ) = 3;  // error
        k = quest( j > 0, i, j );  // ambiguous,因为左值也可以当右值用
        k = quest( j > 0, i, 2 );  // ok
        k = quest( i > 0, 1, j );  // ok
        k = quest( i > 0, 1, 2 );  // ok
}

可以看出和标准答案有两个不相符,就是当true_val和false_val都是左值时,编译器无法区分调用哪个版本。

2 然后我想到了右值引用,一查,右值引用还真有几个比较有趣的特性:
a> 右值引用作为函数参数,只能传递右值或临时变量,不能传递左值或左值引用
b> 如果在模板或者typedef中可以使用引用折叠,折叠规则如下:
- 右值引用的右值引用折叠为右值引用(T&& &&认为是T&&)
- 其它情况都认为是左值引用(T& &&认为是T&)
c> 如果传递的是左值,这时推断T是原型的时候,会组成T&&右值引用,导致绑定错误,这时编译器会聪明的推断输入是T&,从而触发引用折叠,推断出参数最终类型是T&

根据上述特性,我写出了第二个版本:

// 参数使用了右值引用,返回值使用了左值引用
template < typename T >
T& quest( bool cond, T&& true_val, T&& false_val ) {
        if( cond ) return true_val;
        return false_val;
}

编译结果:

void test1() {
        int i = 0, j = 0, k;
        quest( i > 0, i, j ) = 3;  // ok
        quest( i > 0, i, 2 ) = 3;  // error
        quest( i > 0, 1, j ) = 3;  // error
        quest( i > 0, 1, 2 ) = 3;  // ok
        k = quest( j > 0, i, j );  // ok
        k = quest( j > 0, i, 2 );  // error
        k = quest( i > 0, 1, j );  // error
        k = quest( i > 0, 1, 2 );  // ok
}

当传入的参数是一左值一个右值时出错倒知道怎么回事,但是第4个竟然通过了,也就是因为返回的是左值引用,所以即使我传进去两个右值,传出来的居然是一个临时变量的左值引用。

3 然后想到了上面所说c的特点,所以我将返回值写成了T,因为传入的是左值的时候,编译器会为我将T推倒为引用类型。

template < typename T >
T quest( bool cond, T&& true_val, T&& false_val ) {
        if( cond ) return true_val;
        return false_val;
}

可以说,这已经解决了问题的90%了。最后剩下的问题就是当传入的是一个左值一个右值怎么办,我的解决办法是增加两个函数。

最终结果

template < typename T >
T quest( bool cond, T&& true_val, T&& false_val ) {
        if( cond ) return true_val;
        return false_val;
}

template < typename T >
T quest( bool cond, T& true_val, T&& false_val ) {
        if( cond ) return true_val;
        return false_val;
}

template < typename T >
T quest( bool cond, T&& true_val, T& false_val ) {
        if( cond ) return true_val;
        return false_val;
}

参考
[1] http://en.cppreference.com/w/cpp/language/reference
[2] http://www.th7.cn/Program/cp/201403/183896.shtml
[3] http://www.2cto.com/kf/201311/260709.html

你可能感兴趣的:(C++,右值引用)