【问题标题】:Why can't we have automatically deduced return types?为什么我们不能自动推断返回类型?
【发布时间】:2023-03-18 06:16:01
【问题描述】:

最近我和一个朋友一起工作,他想让 C++ 更像 Haskell-y,我们想要一个基本上像这样的函数:

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 不就是重复信息,因为编译器只能看return语句吗?

我认为可能需要它,所以我们可以只包含一个头文件:

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

...但无论如何我们都不能使用这样的模板。

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

案例一:decltype

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

案例2:没有decltype

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

考虑到这些,decltype 的尾随返回类型有什么意义?

【问题讨论】:

  • 我认为这是因为函数的所有类型信息都必须从声明中知道(不包括函数体),而不需要经过定义。
  • 我认为这个问题最好用“为什么我们需要尾随返回类型?”来表述。它们只是decltype 的一个用例,问题与其他任何一个都没有关系。
  • @LucDanton 对,所以应该是“为什么我们不能自动推断返回类型?”
  • @Brendan:简单的回答是可以,但委员会明确或暗示认为不值得付出努力。 C++ 已经是一种过于复杂的语言,添加规则来实现这一点是可能的,但会增加更多的复杂性。也就是说,我同意能够说auto foo(auto x, auto y) { return x + y;} 会很好,这当然是可能的,但它与额外的语法一样可行,只是更烦人;烦恼比新规则更容易处理。也就是说,非常不幸的是,他们没有详细说明推断 lambda 之类的返回类型。
  • 我知道现在已经很晚了,但是 GCC 4.8.0 使用 -std=c++1y 选项对此提供了支持。目前正在提议中。

标签: c++ c++11


【解决方案1】:

好吧 - 自从提出原始问题以来已经过去了一段时间,现在的答案是你可以!

是的,这个问题确实被标记为 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 的答案,出于历史原因保留在这里,还有一些来自未来的 cmet(C++14 及更高版本): p>

如果我们有以下情况:

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 lambdas 已经涵盖了这种情况,只要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 的声明。

【讨论】:

  • 如果 return 语句返回不同的东西,该函数无论如何都不会编译。
  • @BrendanLong:不一定。可以进行隐式转换。但是没有显式类型编译器将不知道要转换什么以及为什么要转换。
  • 从技术上讲,这是一个已解决的问题。 Lambda 允许您省略返回类型,只要所有 return 语句中的 decltype 相同。所以 C++11 已经有了可以解决这个问题的语言;编译器将在 returns 不一致的 lambda 中报告错误。因此,仅此一项并不是规范要求命名函数的尾随返回类型的正当理由。
  • 是的,lambdas 确定返回类型,如果您尝试返回不同的内容,则会失败。也许实际上没有什么可以将这种级别的智能扔给编译器...除了函数声明可能在 SFINATE 中发挥重要作用,并且不明确指定类型可能效果不佳...我不知道不知道。
  • 也许我们应该把我们的答案结合起来,制作一个社区维基。
【解决方案2】:

但无论如何我们都不能使用这样的模板。

首先,尾随返回类型并不是纯粹的模板。它们适用于所有功能。其次,说谁?这是完全合法的代码:

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 没有返回类型的唯一原因是因为它们必须有一个用定义定义的函数体。如果您将尾随返回类型限制为只有定义可用的那些函数,您将无法使用上述任何一种情况。

【讨论】:

  • 理论上虽然可以实现这样的功能,但仅适用于函数产生一种且仅一种类型结果的情况。如果没有找到匹配的定义,则忽略函数......但对于模糊的特征来说,这似乎是一种非常智能的方式......
  • @VladLazarenko:理论上没什么; Lambdas 现在就可以做到。除非 lambda 中不同类型的返回值不一致,否则您不必指定返回类型。规范可以允许它,但也意味着不允许函数的前向声明。
  • 绝对是一个好点。我希望他们只是在您实际使用前向声明时要求这样做。 :\
【解决方案3】:

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

您可以几乎使用 lambdas 获得所需的漂亮语法(但您不能在 lambdas 中使用模板化参数,同样没有充分的理由)。

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

是的,在某些情况下无法自动推断返回类型。就像在 lambda 中一样,在那些模棱两可的情况下,可能只是要求自己声明返回类型。

目前,这些限制非常随意,令人非常沮丧。另请参阅删除概念以增加挫败感。

【讨论】:

  • 你也不能重载函数,使用这个。
【解决方案4】:

在 Dave Abrahams 的 blog post 中,他讨论了使用这种函数语法的提议:

[]min(x, y)
{ return x < y ? x : y }

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

【讨论】:

    【解决方案5】:

    虽然没有技术障碍,但说服委员会添加这样的语言功能的希望不大。但也许可以说服 GCC 小组添加一个编译器扩展。

    【讨论】:

    • 基于这些答案,我不确定 C++ 是否会支持它。 C++ 以一种非常具体的方式解析代码,即使我们可以找出返回类型,它也会违反 C++ 编译器应该工作的规则。 :(
    猜你喜欢
    • 2014-02-11
    • 2019-10-19
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-21
    相关资源
    最近更新 更多