为什么我们不能有自动推断的返回类型?

51

最近我正在与一位想让C++更像Haskell的朋友合作,我们想要一个基本上像这样的函数:

auto sum(auto a, auto b) {
    return a + b;
}

显然我不能使用auto作为参数类型,所以我将其更改为:

template<class A, class B>
auto sum(A a, B b) {
    return a + b;
}

但是那也不行。我们最终意识到需要这个:

template<class A, class B>
auto sum(A a, B b) -> decltype(a + b) {
    return a + b;
}

所以我的问题是,这有什么意义呢?decltype不是重复信息吗,因为编译器可以直接查看返回语句吗?

我考虑过可能需要它,这样我们就可以直接包含头文件:

template<class A, class B>
auto sum(A a, B b) -> decltype(a + b);

...但我们无论如何都不能使用那种模板。

我考虑的另一件事是,这可能会更容易让编译器处理,但实际上它似乎更难了。

情况1:使用 decltype

  • 找出decltype语句的类型
  • 找出任何返回值的类型
  • 看看它们是否匹配

情况2:不用 decltype

  • 找出任何返回值的类型
  • 看看它们是否匹配

因此,考虑到这些问题,有了decltype的尾随返回类型有什么用呢?


6
我认为这是因为函数的所有类型信息必须仅从声明中就能知道(不包括函数体),而不需要查看定义。 - Matteo Italia
2
我认为这个问题最好改为“我们为什么需要尾返回类型?”它们只是decltype的一个用例,而且这个问题没有涉及到其他任何用例。 - pmr
4
@LucDanton,所以它更应该是“为什么我们不能自动推导返回类型?” - pmr
2
@Brendan:简单的答案是它可能,但委员会明确或暗示地得出结论,这不值得努力。C++已经是一个过于复杂的语言了,添加规则来允许这样做可能会增加更多的复杂性。也就是说,我同意能够说auto foo(auto x, auto y) { return x + y;}是很好的,而且肯定是可行的,但是使用额外的语法也可以做到,只是更烦人;烦恼比新规则更容易处理。话虽如此,他们没有详细说明推断返回类型对于诸如lambda之类的事情是极其不幸的。 - GManNickG
5
我知道现在时间有点晚了,但是GCC 4.8.0已经支持这一功能了,使用 -std=c++1y 选项即可。目前这个功能正在被提出。 - chris
显示剩余4条评论
4个回答

38

一段时间过去了,自提出问题以来现在的答案是可以的!

没错,问题标记为C++11 - 在此版本中仍不能做到OP所要求的。但值得一提的是,现在我们可以使用C++14或更高版本实现这个需求。

C++14开始,以下代码是有效的:

template<class A, class B>
auto sum(A a, B b) {
    return a + b;
}

自C++20以来,这也是有效的:

auto sum(auto a, auto b) {
    return a + b;
}

以下是C++11的答案,因为历史原因而保留,并附有一些来自未来(C++14及以后版本)的注释:
如果我们有以下内容:
template<class A, class B, class C>
auto sum(A a, B b, C c) {
   if (rand () == 0) return a + b;

   // do something else...

    return a + c;
}

..其中a + ba + c表达式产生不同类型的结果。 编译器应该决定将什么作为该函数的返回类型,以及为什么? 这种情况已经被C++11 lambda覆盖,只要return语句可以被推导到相同的类型(需要注意的是标准引用,一些来源声称只允许一个返回表达式,并且这是gcc的故障)。


来自未来的注释(C++14及以上版本):上面的示例仍然无效,您只能拥有一个可能的返回类型。但是,如果有不同的返回类型,但实际返回类型可以在编译时推断出,则我们有两个不同的函数,这是有效的。例如,以下示例自C++17起是有效的:

template<class A, class B, class C>
auto sum(A a, B b, C c) {
    if constexpr(std::is_same_v<A, B>) return a + b;
    else return a + c;
}

int main() {
    auto a1 = sum(1, 2l, 3.5); // 4.5
    auto a2 = sum(1, 2, 3.5); // 3
}

回到原始的 C++11 答案,解释为什么不支持所请求的语法:

技术上的原因是 C++ 允许定义和声明分开。

template<class A, class B>
auto sum(A a, B b) -> decltype(a + b);

template<class A, class B>
auto sum(A a, B b) -> decltype(a + b)
{
}

模板的定义可以在头文件中,也可以在另一个文件中。这样,当查看接口时,您不必浏览页面和页面的函数定义。
C++必须考虑所有可能性。将尾随返回类型限制为函数定义意味着您无法执行像这样简单的操作:
template<class A, class B>
class Foo
{
  auto sum(A a, B b) -> decltype(a + b);
}

template<class A, class B>
auto Foo<A, B>::sum(A a, B b) -> decltype(a + b)
{
}

来自未来的一条注释(C++14及以上版本):如果编译器在调用时没有看到定义,你仍然不能使用auto返回类型进行声明。


2
如果返回语句返回不同的内容,该函数无论如何都无法编译。 - Brendan Long
11
不一定。可能会发生隐式转换。但是如果没有明确的类型,编译器就不知道要进行什么样的转换和原因。 - user405725
15
从技术上讲,这个问题已经解决了。Lambda允许你省略返回类型,只要所有return语句的decltype相同即可。因此,C++11已经有了可以解决这个问题的语言特性;编译器会报告lambda中return不一致的错误。因此,这并不是规范要求在命名函数中需要使用尾置返回类型的有效理由。 - Nicol Bolas
2
真实的情况是,lambda表达式确定返回类型,并且如果您尝试返回不同的内容,则会失败。也许没有什么东西实际上阻止编译器投入这种智能水平...除了函数声明可能在SFINATE中发挥重要作用,而不明确指定类型可能不会很好地工作...我不知道。 - user405725
1
也许我们应该把我们的答案结合起来,制作一个社区维基。 - user405725
显示剩余6条评论

8

但是我们无论如何都不能像那样使用模板。

首先,尾置返回类型并不仅仅适用于模板。它们适用于所有函数。其次,谁说的?下面这段代码是完全合法的:

template<class A, class B>
auto sum(A a, B b) -> decltype(a + b);

template<class A, class B>
auto sum(A a, B b) -> decltype(a + b)
{
}

模板的定义可以在头文件中,也可以在另一个文件中,这样您在查看接口时就不必浏览数页函数定义。

C++必须考虑所有可能性。将尾随返回类型限制为函数定义意味着您无法执行像这样简单的操作:

template<class A, class B>
class Foo
{
  auto sum(A a, B b) -> decltype(a + b);
}

template<class A, class B>
auto Foo<A, B>::sum(A a, B b) -> decltype(a + b)
{
}

许多程序员都喜欢这样编码,这是相当常见的。没有想要以这种方式编码的问题。

只有Lambda能够在不带返回类型的情况下工作,这是因为它们必须使用定义来定义函数体。如果你把尾随返回类型限制在只有定义可用的那些函数中,你将不能使用上述任何一种情况。


理论上虽然可以实现这样的功能,但只适用于函数产生一种且仅一种类型结果的情况。如果找不到匹配的定义,则忽略该函数...但对于一个模糊的特性来说,这似乎需要过多的智能。 - user405725
2
@VladLazarenko:理论上没有什么问题;Lambda现在就可以做到。除非lambda返回值的不同类型不一致,否则您无需指定返回类型。规范本来可以允许它,但这也意味着禁止函数的前向声明。 - Nicol Bolas
绝对是一个好观点。我希望他们只在实际使用前向声明时要求这个。:\ - Brendan Long

5

没有技术上的原因不允许这样做。他们没有这样做的主要原因是C ++语言进展非常缓慢,需要很长时间才能添加功能。

使用lambda表达式可以接近您想要的优美语法(但出于没有充分理由,无法在lambda表达式中使用模板参数)。

auto foo = [](int a, double b)
{
    return a + b;
};

是的,有一些情况下返回类型无法自动推断。就像在lambda表达式中一样,在这些不明确的情况下,你可能需要自己声明返回类型。
目前,这些限制太过武断,令人非常沮丧。另外,还要看到概念被移除,更加令人沮丧。

你也不能使用这种方法重载函数。 - Paul Fultz II

0
在 Dave Abrahams 的博客文章中,他讨论了一项关于使用此函数语法的提案:
[]min(x, y)
{ return x < y ? x : y }

这是基于可能的多态 lambda 的提案。他还开始在这里更新 clang 以支持这种语法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接