在C ++ 11中省略返回类型

时间:2010-12-24 01:05:46

标签: c++ c++11 return-value

我最近发现自己在C ++ 11模式下使用gcc 4.5的以下宏:

#define RETURN(x) -> decltype(x) { return x; }

编写这样的函数:

template <class T>
auto f(T&& x) RETURN (( g(h(std::forward<T>(x))) ))

我一直这样做是为了避免不得不两次有效地编写函数体,并保持身体和返回类型的同步(这在我看来是等待发生的灾难)的不便。

问题是这种技术只适用于一行函数。所以当我有这样的事情时(错综复杂的例子):

template <class T>
auto f(T&& x) -> ...
{
   auto y1 = f(x);
   auto y2 = h(y1, g1(x));
   auto y3 = h(y1, g2(x));
   if (y1) { ++y3; }
   return h2(y2, y3);
}

然后我必须在返回类型中加入一些可怕的东西。

此外,每当我更新函数时,我都需要更改返回类型,如果我没有正确更改它,如果我很幸运,或者运行时出现错误,我将收到编译错误更糟糕的情况下。必须将更改复制并粘贴到两个位置并保持同步,我觉得这不是一个好习惯。

我想不出一种情况,我希望在返回时使用隐式强制转换,而不是显式强制转换。

当然有一种方法可以要求编译器推断出这些信息。编译器保守秘密有什么意义?我认为C ++ 11的设计不需要这样的重复。

7 个答案:

答案 0 :(得分:23)

g++ 4.8似乎正在获得自动返回类型扣除的实现。 这个补丁由杰森·梅里尔(Jason Merrill)投入,他也在发送C ++的论文 - 1Y用于该功能。该功能适用​​于-std = c ++ 1y。

还在玩它。

答案 1 :(得分:11)

这种行为的基本原理在草案8.3.5p12中给出:

  

尾随返回类型最有用   对于更多的类型   之前复杂的指定   说明符ID:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);
  

而不是

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

所以这实际上只是为了简化引用参数名称有帮助的情况。

如果你假设C ++可以总是从函数体中推断函数的返回类型:这不会飞。 C ++(和C)的目标是通过将声明与实现分离来实现模块化,因此在调用时,您可能没有可用的函数体。但是,每个调用者都需要知道所调用的每个函数/方法的参数类型和返回类型

答案 2 :(得分:7)

如果您只是尝试设置返回类型,请将其设为模板参数。这样,您可以更改与返回类型相关的所有内容,而无需实际更改功能。如果您希望在此示例中使用,则可以设置默认返回类型。

template <class R = int, class T>
R f(T&& x)
{
   ...
   return h2(y2, y3);
}

以下代码证明了它的有效性。

DEMO CODE:

#include <iostream>
#include <iomanip>

template <class T, class S>
T h2(T& x, S& y)
{
  return x + y;
}

template <class R = int, class T>
R f(T& x)
{
  auto y2 = x;
  auto y3 = x;
  return h2(y2, y3);
}

int main(int argc, char** argv)
{
  int x = 7;
  std::string str = "test! ";

  auto d = f<double>(x);
  auto i = f(x); // use default type (int)
  auto s = f<std::string>(str);

  std::cout << std::fixed << std::setprecision(4);
  std::cout << "double: " << d << std::endl;
  std::cout << "int: " << i << std::endl;
  std::cout << "string: " << s << std::endl;

  return 0;
}

<强>输出:

double: 14.0000
int: 14
string: test! test!

不幸的是,您正在寻找的确切功能尚未存在,并且不是C ++ 0x规范的一部分。但是,在草拟时,这可能是C ++ 1x规范的一部分。在那之前,坚持模板。

答案 3 :(得分:6)

编辑:oops,我刚刚意识到 trailing-return-type 说明符和return语句之间存在范围差异。具体做法是:

auto f(int a)
{
    char r[sizeof(f(a))+1];
    return r;
}

KABOOM!


上一个回答:

不幸的是,在这种情况下,语言没有提供编译器推断返回类型的语法,因为显示推理是可能的。

具体来说,我们讨论的是函数中只有一个return语句的情况。

独立于函数返回语句的位置,或前面代码的复杂程度,应该清楚以下转换是可能的:

return (ugly expression);

auto return_value = (ugly expression);
return return_value;

如果编译器可以推断return_value的类型(并且根据C ++ 0x规则,它可以),那么可以选择推断类型return_value作为返回类型功能

因此,在我看来,对C ++ 0x进行修改时,只有在返回语句的多样性不是一个时才需要尾随返回类型说明符才是可行的并解决问题。

答案 4 :(得分:3)

我同意Yttrill。返回类型推导已经被证明是像Haskell这样的语言的可行实践,并且由于C ++已经实现了'auto',它只是进一步实现返回类型推导。这种推论应该在专业化时发生,而不是模板定义,因为需要提供给模板的真实类型的信息。声明和定义的分离不再是通用C ++中的常见做法,因为模板体必须写在头文件中,因此模板体几乎总是使用模板声明。在存在多个返回语句并且它们的类型不匹配的情况下,编译器可以愉快地报告错误。总之,如果委员会愿意,在C ++中完全可以使用返回类型推导。这非常重要,因为手动编写返回类型的重复阻碍了小型通用辅助函数的普遍使用,这在函数式和泛型编程中是常见的做法。

答案 5 :(得分:2)

许多编程语言(包括Ocaml和Felix)都可以推断出函数的返回类型,并且不需要指定它。在Ocaml中,您可以并且必须在界面中指定它。在Felix中我找到了一些函数,明智的做法是在库代码中指定它以使其更易于使用。

我很惊讶“自动”不适用于返回类型,它当然是在盘子上。这被认为太难实施了吗? [鉴于函数可以递归,这并非易事。)

哦,现在我明白了。问题是愚蠢的模板设计功能“依赖名称”:

template<class T> auto f(T a) { return a.f(); }

因此,虽然由于重载而计算普通函数的返回类型有点棘手,但由于依赖名称查找而无法使用模板:它只能在实例化之后完成。但是在此之前必须发生超载。因此,自动返回类型不能使用该语言,因为它不会推广到模板。

答案 6 :(得分:-3)

您的退货类型是否真的经常改变?为什么不能明确指定它? 例如:

template<class T>
int f(T&& x)
{
...
}

人们这样做了二十多年......