【问题标题】:C++ function returning functionC++函数返回函数
【发布时间】:2015-10-01 22:42:02
【问题描述】:

标准中哪些地方不允许函数返回函数?我知道它们在概念上很荒谬,但在我看来,语法允许它们。根据此网页,“noptr-declarator [is] any valid declarator”将包含函数声明符:

int f()();

关于语法。

在我看来,如 [dcl.decl] 中所述的语法允许

int f(char)(double)

可以解释为函数 f 接受一个 char 并返回一个与 @987654326签名相同的函数@。

1    declarator:
2       ptr-declarator
3       noptr-declarator parameters-and-qualifiers trailing-return-type
4    ptr-declarator:
5        noptr-declarator
6        ptr-operator ptr-declarator
7    noptr-declarator:
8        declarator-id attribute-specifier-seq opt
9        noptr-declarator parameters-and-qualifiers
10       noptr-declarator [ constant-expression opt ] attribute-specifier-seq opt
11       ( ptr-declarator )
12    parameters-and-qualifiers:
13       ( parameter-declaration-clause ) cv-qualifier-seqAfter

粗略地说,之后 1->2, 2=4, 4->6, 4->6 你应该有 ptr-operator ptr-operator ptr-operator 然后,使用 4->5, 5=7, 7->8 作为第一个声明符;对第二个和第三个声明符使用 4->5, 5=7, 7->9。

【问题讨论】:

  • 这真是糟糕的建议。即使在 C 中,您也可以返回函数指针。在 C++ 中,您可以使用 std::function 或任何可调用对象。
  • @maja:那不返回函数。它返回一个函数指针。
  • 您错误地认为 C++ 主要由语法定义。语法意义不大(众所周知,它对 C++ 来说非常模棱两可;这就是为什么编写 C++ 解析器非常困难,阅读 C++ 代码也是如此!)
  • 我最终否决了这个问题,因为它混合了 grammarlanguage 定义,这对于 C++ 来说(遗憾的是恕我直言)是非常不同的。这就是为什么 C++ 如此难以阅读、难以学习、难以解析(所有这一切都是因为 legacy与 C 的向后外观兼容性 的原因)
  • 函数返回函数在概念上没有什么可笑的。它一直用其他语言完成。它甚至在 C++ 中以函数返回闭包的形式完成。

标签: c++ c++11 standards language-lawyer


【解决方案1】:

来自 [dcl.fct],非常明确:

函数的返回类型不应为数组或函数,尽管它们的返回类型可能为 类型指针或对此类事物的引用。不能有函数数组,尽管可以有数组 指向函数的指针。

使用 C++11,您可能只想:

std::function<int()> f();
std::function<int(double)> f(char);

关于 C++ 语法存在一些混淆。语句int f(char)(double);可以按照语法进行解析。这是一个解析树:

此外,基于 [dcl.fct]/1 的这种解析甚至是有意义的:

T D 的声明中,D 的格式为
D1 ( parameter-declaration-clause ) cv-qualifier-seqopt
ref-qualifieroptexception-specificationoptattribute-specifier-seq选择
并且声明 T D1 中包含的 declarator-id 的类型是“derived-declarator-type-list T”,即 Ddeclarator-id 的类型是“derived-declarator-type-list 函数的 (parameter-declaration-clause ) cv-qualifier-seqoptref-qualifieropt 返回T”。

在此示例中,T == intD == f(char)(double)D1 == f(char)T D1 (int f(char)) 中 declarator-id 的类型是“(char) 返回 int 的函数”。所以 derived-declarator-type-list 是“(char) 返回函数”。因此,f 的类型将被解读为“(char) 返回函数的 (double) 返回 int 的函数”。

这最终是无稽之谈,因为这是明确不允许的声明符形式。但不是由语法。

【讨论】:

  • 注意它不是一个语法的东西。 C++ 语法(准 EBNF 表示法)可能允许返回函数。语义禁止它。
  • 标准超过1000页。很容易忽略一些东西。在这种情况下,那是第 10 段……太低了……
  • @Hector 这就是我们有带书签的 PDF 的原因。跳转到Functions部分(8.3.5),然后搜索“return”
  • @Barry:没必要居高临下。我确实在 8.4 中搜索过,然后是 8.3.5 的前两页。到那时,我以为我什么都不懂。
  • @Hector 我无意居高临下。我以为您正在阅读纸质版本,并建议使用更方便的替代方法。
【解决方案2】:

使用C++11(但不是以前的C++ 版本),您不仅可以返回类似C 的函数指针,还可以返回C++ closures,尤其是anonymous functions。另见std::function

标准不允许(语义,而不是语法 - 所以不是语法的问题> ;参见Barry's answer 的引用)返回函数(也不允许sizeof 使用函数!)但允许返回函数指针。 p>

顺便说一句,我不认为你可以返回整个函数。那意味着什么?你将如何实现它?实际上,函数是一些代码块,它的名称(就像数组一样)是指向函数机器码开头的指针。

一个不错的技巧可能是在运行时构建(使用 C++ 标准之外的机制)一个函数(然后处理它的函数指针)。一些外部库可能允许这样做:您可以使用 JIT 库(例如 asmjitgccjitLLVM ...)或简单地生成 C++ 代码,然后在 POSIX 系统上编译和 dlopendlsym 它等。

PS。您可能正确理解 C++11 grammar(标准中的 EBNF 规则)不允许返回函数。这是一个用简单的英语陈述的语义规则,它不允许这样做(它不是任何语法规则)。我的意思是单独的 EBNF 将允许:

 // semantically wrong... but perhaps not syntactically
 typedef int sigfun_T(std::string);
 sigfun_T foobar(int);

并且由于semantics 的原因(不是因为EBNF 规则),编译器正确地拒绝了上述代码。实际上,symbol table 对 C++ 编译器很重要(它不是 语法或上下文无关语法)。

关于 C++ 的可悲事实是(由于遗留原因)它的语法(单独)非常模糊。因此,C++11 难以阅读(对于人类),难以编写(对于开发人员),难以解析(对于编译器),......

【讨论】:

  • “标准禁止[s]返回函数”没有回答“标准的哪一部分禁止[s]返回函数”的问题。
  • “顺便说一句,我不认为你可以返回整个函数。这意味着什么?你将如何实现它。” 对,OP 在题。今天没有人可以阅读:(
  • 其实,使用 jit 库,你可以返回一个函数;或者至少包含一个内存块。
  • @CofeeandCode:从技术上讲,JIT 库将返回一个函数指针并在地址空间中构建一个函数代码。
  • @LightnessRacesinOrbit:我会再次为魔鬼辩护。对寄存器中保存的内容的解释不是 C++ 的一部分。它可能是一个函数。我正在解决关于 JIT 库返回指向函数而不是函数的指针的 cmets。
【解决方案3】:
using namespace std;

auto hello()
{
    char name[] = "asdf";
    return [&]() -> void
    {
        printf("%s\n", name);
    };
}

int main()
{
    hello()();
    auto r = hello();
    r();
    return 0;
}

【讨论】:

  • 虽然此代码可能会解决问题,including an explanation 关于如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。
  • 不好意思,我是c++菜鸟,只知道这样写代码可以解决问题,但是不明白这段代码是怎么工作的
【解决方案4】:

实际上在 C 中不能传递或返回函数。只能传递/返回函数的指针/地址,这在概念上非常接近。老实说,由于可以用函数指针省略&amp;*,因此人们不应该真正关心是否传递了函数或指针(除非它包含任何静态数据)。这是简单的函数指针声明:

void (*foo)();

foo 是指向返回 void 且不带参数的函数的指针。

在 C++ 中并没有太大的不同。对于所有可调用的创建,仍然可以使用 C 风格的函数指针或新的有用的 std::function 对象。 C++ 还添加了lambda expressions,它们是内联函数,其工作方式类似于函数式语言中的闭包。它允许您不要将所有传递的函数设为全局。

最后——返回函数指针或std::function 可能看起来很荒谬,但事实并非如此。例如,状态机模式(在大多数情况下)基于返回指向处理下一个状态的函数或可调用的下一个状态对象的指针。

【讨论】:

    【解决方案5】:

    C 的形式语法实际上不允许返回函数,但是,总是可以返回一个函数指针,就所有意图和目的而言,它看起来就像你想要做的:

      int (*getFunc())(int, int) { … }
    

    我坚持说,语法是缺乏对此类功能的支持的更基本的解释。标准的规则是后一个问题。

    如果语法没有提供完成某事的方法,我认为语义或标准对任何给定的上下文无关语言所说的内容并不重要。

    【讨论】:

    • @LightnessRacesinOrbit 我的意思是“通过函数指针返回函数”。
    • “标准不允许[s]返回函数”没有回答“标准的哪一部分不允许[s]返回函数”的问题。
    • 我编辑了我的答案以改进它。以typedef int sigfun_T(std::string); 开头的代码块是合法的 C++ syntax 但它没有含义
    • 我投了反对票,因为返回一个函数是被不是语法(而是“语义”)的东西所禁止的。
    • 我发布了一个解析树来说明语法确实允许这样做。
    【解决方案6】:

    除非您返回一个指针或对函数的引用,这是可以的,否则替代方法是返回一个函数的副本。现在,想一想函数的副本是什么样子、行为如何、行为如何。那首先是一个字节数组,这也是不允许的,第二个字节是一段代码的等价物,实际上是返回一段代码……几乎所有的启发式病毒扫描程序都会将其视为病毒,因为也无法验证运行时系统甚至在编译时返回的代码的可行性。即使你可以返回一个数组,你将如何返回一个函数?返回数组(将是堆栈上的副本)的主要问题是大小未知,因此无法将其从堆栈中删除,并且函数存在相同的困境(数组将是机器语言二进制代码)。另外,如果您确实以这种方式返回了一个函数,您将如何转身调用该函数?

    总而言之,返回函数而不是指向函数的点的概念失败了,因为该概念是放置(复制)到堆栈上的未知大小的机器代码数组。这不是 C 或 C++ 设计允许的,现在有了代码可以转身调用该函数,尤其是我想传递参数。

    我希望这是有道理的

    【讨论】:

      【解决方案7】:

      在某种意义上,function_pointer 是它自己的函数,
      而“尾随返回类型”在 c++11 中是一个很好且隐藏的东西,我宁愿这样写:

      #include<iostream>
      
      
      
      auto return0(void)->int
      {return 0;}
      auto returnf(void)->decltype(&return0)
      {return &return0;}
      
      
      
      /*Edit: or...
      auto returnf(void)->auto (*)(void)->int
      {return &return0;}
      //that star means function pointer
      */
      
      
      
      
      auto main(void)->int
      {
          std::cout<<returnf()();
          return 0;
      }
      

      (如果类型不匹配,请尝试应用地址运算符“&”)
      看,很有道理。

      但不利的一面是函数头中的“自动”事物,
      那是不可移动的,没有类型可以匹配它(甚至 lambda 可以匹配模板类型 std::function) 但如果你愿意,宏可以对你施魔法(有时是诅咒)

      #define func auto
      

      【讨论】:

        猜你喜欢
        • 2015-10-31
        • 2013-04-03
        • 1970-01-01
        • 2016-08-28
        • 2012-06-04
        • 2012-09-26
        • 2014-12-05
        • 1970-01-01
        相关资源
        最近更新 更多