【问题标题】:Why is this "min" template of cpp-next at fault?为什么这个 cpp-next 的“最小”模板有问题?
【发布时间】:2012-01-01 23:14:33
【问题描述】:

我正在阅读 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 模板不起作用:它返回一个引用(因为参数是一个左值),该引用最终在最常见的用途中引用了一个局部变量。

我认为每个人可能都不清楚问题是如何表现出来的。能否给个详细的解释和可能的修复方法?

【问题讨论】:

  • 我在等你现在发布答案,拜托...... :)
  • 我有点好奇如果 T 和 U 是不同的类型会发生什么?那还能用吗?
  • @ronag:如果它们能以某种方式转换为一个共同的“基础”。 :) float 和 int 都应该可以转换为 float,这将是表达式的类型。
  • 所以最后,对于我们这些不太了解细节的人(例如@Potatoswatter 中的代码对我来说太复杂了),我们应该写什么?一个只有一个模板参数的min 函数(即没有计算返回类型的技巧)?
  • 对于 C++03,promote 模板可用于计算返回类型:)

标签: c++ c++11 decltype


【解决方案1】:

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

【讨论】:

  • +1 表示正确的想法——我想第一句话不缺少否定句
  • 我不同意。问题是通过引用返回,因为那样你可以这样做:float&amp; f = std::min(3.0f, 1); 这是不正确的。
  • +1 尝试为 C++11 执行此操作但失败:open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2199.html
  • @Matthieu:不,你不能,因为?: 的两个参数都是右值时的结果是一个右值。
  • @DaveAbrahams:但不尝试返回引用是错误的。例如,如果ab 都是int,您应该能够执行std::min(a, b) = 5; 之类的操作。
【解决方案2】:

第 3 版:康拉德鲁道夫

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; 
}

第 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 编译的。

【讨论】:

  • 不,TU 将是 int
  • Xeo:是的,你是对的。我几乎与自己的原因和修复相矛盾。我改变了答案。
  • 这当然需要TU 是默认可构造的。难道我们不能使用对某个id 函数的调用来从xy 获取一个临时值来强制使用非引用吗? …或者只是remove_reference
  • @KonradRudolph:std::declval&lt;T&gt;().
  • @KennyTM 但是这会返回一个右值引用,因此我们遇到了和以前一样的问题,不是吗?
【解决方案3】:

参数是按值传递的(TU 推导出为 int),但在这种情况下,?: 表达式的类型被推导出为引用,因为它们是函数内部的本地左值。 @Johannes 的答案将在几分钟后提供。 :D

【讨论】:

  • 我很困惑 :( 我不知道这一切背后的棘手规则。
  • @Johannes:当然。我相信每个人都会相信你不知道。 ;)
  • 为什么推断为参考?下一个代码说没有 void f2(int x, long y) {cout ::value
  • @yola,那是因为intlong 是完全不同的类型。没有单一的引用类型可以链接到两者。但是如果 x 和 y 是相同的整数类型,那么它将是一个引用。
【解决方案4】:

有什么大惊小怪的,为什么没有人尝试显而易见的解决方案,即完美转发?

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”。如果在函数签名中以任何方式使用统一初始化,这似乎会发生。

【讨论】:

  • @DeadMG 已经给出了这个解决方案,尽管他缺少代码:)
  • @JohannesSchaub-litb 已更新以添加霍华德的一项功能。
【解决方案5】:

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

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&lt;y?x:y)=0 将最小的变量设置为 0,而不要理会另一个变量。当然,你不能做(1&lt;3?6:8)=0,因为你不能做6=08=0。所以在这种情况下它只是一个右值。

在 min 中,x 和 y 是函数参数的名称,因此是左值。 decltype(x&lt;y?:x:y)int&amp;。 (我发现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 的参数是引用。我想这有点品味问题。如果您只想返回非引用,这很容易在decltype(x&lt;y?x:y) 周围使用std::remove_reference 来执行。但这很无聊。让我们允许自己(有时)返回引用;在许多情况下,它可能会更有效和有用。

如果您使用 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 的行为?那么,我们如何实现呢?

【讨论】:

  • 接下来的几个小时我都不会上网。随意广泛编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多