为什么cpp-next的这个“min”模板有问题?

时间:2011-11-19 15:49:34

标签: c++ c++11 decltype

我正在阅读cpp-next,其中显示了这个min模板,作为如何将详细的C ++代码与python代码进行比较的示例

template <class T, class U>
auto min(T x, U y)->decltype(x < y ? x : y)
{ return x < y ? x : y; }

起初这看起来很无辜,但Daveed Vandevoorde发表了这句话

  

在其返回类型规范中使用decltype的min模板不起作用:它返回一个引用(因为参数是一个左值),它最终引用了大多数常见用途的局部变量。

我认为每个人可能都不清楚问题是如何表现出来的。您能否详细解释一下并提供可能的解决方案?

5 个答案:

答案 0 :(得分:8)

问题是参数不作为参考。这将调用切片,在多态类型的情况下,然后引用返回到局部变量。解决方案是将参数作为右值引用,调用完美转发,然后简单地推导并返回返回类型。完成后,返回引用就好了,因为值仍然存在。

答案 1 :(得分:6)

rev 3:KonradRudolph

template <class T, class U>
auto min(T x, U y) -> typename std::remove_reference<decltype(x < y ? x : y)>::type
{ 
    return x < y ? x : y; 
}

rev 2:KennyTM

template <class T, class U>
auto min(T x, U y)->decltype(x < y ? std::declval<T>() : std::declval<U>())
{ 
    return x < y ? x : y; 
}

rev 1:T和U必须是默认的可构造的

template <class T, class U>
auto min(T x, U y)->decltype(x < y ? T() : U())
{ 
    return x < y ? x : y; 
}

试验:

int main()
{
   int x; int y;
   static_assert(std::is_same<decltype(min(x, y)), int>::value, "");
   return 0;
}

编辑:

我有点惊讶,但它实际上是用remove_reference编译的。

答案 2 :(得分:3)

参数按值(TU推导为int)传递,但?:表达式的类型在这种情况下被推断为参考,因为它们是是函数内的本地左值。具体细节将在@Johannes的回答中,应该在几分钟内完成。 :d

答案 3 :(得分:3)

什么都是大惊小怪,为什么没有人尝试这个明显的解决方案,这是完美的转发?

template <class T, class U>
typename std::enable_if< ! std::is_integral< T >() || ! std::is_integral< U >(),
                         typename std::common_type< T, U >::type >::type
min(T &&x, U &&y)
    { return x < y ? std::forward< T >( x ) : std::forward< U >( y ); }

template <class T, class U>
decltype( typename std::enable_if< std::is_integral< T >() && std::is_integral< U >(),
                         decltype( typename std::common_type< T, U >
         ::type{ U( -1 ) } ) >::type{ T( -1 ) } )
min(T &&x, U &&y)
    { return x < y ? std::forward< T >( x ) : std::forward< U >( y ); }

现在它的工作方式就像你将表达式放在调用函数中一样,这正是用户期望的(而且只是整体上最好的东西)。

编辑现在,根据霍华德的论文,它禁止危险的无符号与已签名操作,要求每个操作数类型到结果类型的转换为非缩小,如果两个操作数都是整数类型的话。但是,GCC不会编译这个,抱怨“sorry, unimplemented: mangling constructor。”如果在函数签名中以任何方式使用统一初始化,这似乎就会发生。

答案 4 :(得分:1)

通过引用返回有时可能是一个功能,而不是一个错误。我们稍后会回到这里。首先回顾一下基础知识:

int x; int y;
x    // this is an lvalue
y    // lvalue also
x+y  // not an lvalue - you couldn't do (x+y) = 3
x<y?x:y // lvalue - you can do (x<y?x:y) = 0

最后一行显示?:通常可以是左值。即你可以(x<y?x:y)=0将最小的变量设置为0,而另一个变量保持不变。当然,由于您无法(1<3?6:8)=06=0,您无法8=0。所以在这种情况下它只是一个右值。

在min内,x和y是函数参数的名称,因此是左值。 decltype(x<y?:x:y)int&。 (我发现this other cpp-Next article也很有用。)

那么为什么这会成为一个问题呢?好吧,如果min的返回类型是引用,那么它将返回对xy之一的函数参数的引用。现在的问题是,x和y引用自己吗?

考虑这个用例:

int m = 5; int n = 10;
min(m,n) = 0; // do you want this to work?

我们有决定。也许我们希望min返回引用,如果min的参数是引用。我想这有点像味道。如果您非常想要仅返回非引用,则可以使用std::remove_reference周围的decltype(x<y?x:y)轻松执行此操作。但那很无聊。让我们自己(有时)返回引用;在许多情况下,它可能更有效,更有用。

如果使用min的原始示例定义以及x或y的非引用类型,则min将返回对其参数中的本地值的引用。这很糟糕,因为引用无效且行为未定义。例如,这会很糟糕:

int p = min(5,8); // reading from a now-invalid reference.

因此,我们必须经历各种用例并决定我们想要的行为:

// Desired behaviour
int m = 5;
int n = 10;
min(3,7); // return by value. i.e. return an int
min(m,n); // return an int& which maps to either m or n
min(3,n); // return by value
min(foo(), bar()) // what makes sense here?

我们都能就这样的min我们想要的行为达成一致吗?然后,我们如何实现它?