问题引出
今天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