【问题标题】:Self referential type自引用类型
【发布时间】:2011-03-04 04:25:22
【问题描述】:

什么类型的 T 使得下面的代码可以编译?

T f(){ return &f; }

我更喜欢 C 答案,但我将问题标记为 C 和 C++,以防只有使用模板的答案。

【问题讨论】:

  • 你可以在 C/C++ 中做任何事情 :-) 这并不总是一个好主意。
  • 我本来想说void*,但我及时记得C99标准不允许将函数指针转换为void*。指向返回 void* 的函数的指针可能会起作用。
  • 它必须是指向函数的指针,所以我认为 Pascal 是对的,但我不是 C99 的 bod...

标签: c++ c types self-reference


【解决方案1】:

C 编译器后端没有理由不能处理这个问题,而幸运的前端(除了解析器和符号解析传递之外)也可以。但是定义 C 的方式使得将数据获取到编译器的后续位是不可能的。 OTOH 是一个 C 编译器,它通过引用实现 typdefs,并且在解析它们使用的符号之前不符合将 typedefs 填充到符号表中的程度,实际上会很好地吞下它。

【讨论】:

    【解决方案2】:

    有趣的问题,但我猜没有可接受的 C 解决方案。

    为什么这在 C 中是不可能的? (这里有一些猜测)

    返回 T 的函数的类型是:

    T (*)(void) ;
    

    当然,它期望定义 T...但是,由于 T 是函数本身的类型,因此存在循环依赖关系。

    对于结构体 T,我们可以:

    struct T ;               /* forward declaration */
    typedef T * (*f)(void) ; /* f is a function returning a pointer to T */
    

    下一个符号不是很方便吗?

    function T ; /* fictional function forward-declaration.
                    It won't compile, of course */
    T T(void) ;  /* function declaration */
    

    但由于无法前向声明函数,因此无法使用您在问题中编写的构造。

    我不是编译器律师,但我相信这种循环依赖的创建只是因为 typedef 表示法,而不是因为 C/C++ 限制。毕竟,函数指针(我在这里说的是函数,而不是对象方法)都具有相同的大小(以相同的方式结构或类指针都具有相同的大小)。

    研究 C++ 解决方案

    至于 C++ 解决方案,以前的答案给出了很好的答案(我正在考虑zildjohn01's answer,这里)。

    有趣的一点是,它们都基于结构和类可以前向声明的事实(并且在它们的声明体中被认为是前向声明的):

    #include <iostream>
    
    class MyFunctor
    {
       typedef MyFunctor (*myFunctionPointer)() ;
       myFunctionPointer m_f ;
       public :
          MyFunctor(myFunctionPointer p_f) : m_f(p_f) {}
          MyFunctor operator () ()
          {
             m_f() ;
             return *this ;
          }
    } ;
    
    MyFunctor foo()      {
       std::cout << "foo() was called !" << std::endl ;
       return &foo ;
    }
    
    MyFunctor barbar()   {
       std::cout << "barbar() was called !" << std::endl ;
       return &barbar ;
    }
    
    int main(int argc, char* argv[])
    {
       foo()() ;
       barbar()()()()() ;
       return 0 ;
    }
    

    哪些输出:

    foo() was called !
    foo() was called !
    barbar() was called !
    barbar() was called !
    barbar() was called !
    barbar() was called !
    barbar() was called !
    

    从 C++ 解决方案中获得 C 解决方案的灵感

    难道我们不能在 C 中使用类似的方式来获得可比较的结果吗?

    不知何故,是的,但结果不像 C++ 解决方案那样性感:

    #include <stdio.h>
    
    struct MyFuncWrapper ;
    
    typedef struct MyFuncWrapper (*myFuncPtr) () ;
    
    struct MyFuncWrapper { myFuncPtr f ; } ;
    
    struct MyFuncWrapper foo()
    {
       printf("foo() was called!\n") ;
    
       /* Wrapping the function */
       struct MyFuncWrapper w = { &foo } ; return w ;
    }
    
    struct MyFuncWrapper barbar()
    {
       printf("barbar() was called!\n") ;
    
       /* Wrapping the function */
       struct MyFuncWrapper w = { &barbar } ; return w ;
    }
    
    int main()
    {
       foo().f().f().f().f() ;
       barbar().f().f() ;
    
       return 0 ;
    }
    

    哪些输出:

    foo() was called!
    foo() was called!
    foo() was called!
    foo() was called!
    foo() was called!
    barbar() was called!
    barbar() was called!
    barbar() was called!
    

    结论

    您会注意到 C++ 代码在语义上与 C 代码非常相似:每个源将使用一个结构作为指向函数指针的容器,然后,如果需要,使用包含的指针再次调用它。当然,C++ 解决方案使用 operator() 重载,将符号私有化,并使用特定的构造函数作为语法糖。

    (这就是我找到 C 解决方案的方式:尝试“手动”重现 C++ 解决方案)

    我不相信我们可以通过使用宏来改进 C 解决方案的语法糖,所以我们坚持使用这个 C 解决方案,我觉得它远非令人印象深刻,但在我找到它的过程中仍然很有趣.

    毕竟,寻找奇异问题的解决方案是一种可靠的学习方式……

    :-)

    【讨论】:

      【解决方案3】:

      正如C FAQ Question 1.22 解释的那样,这在C 中是不可能的。解决方法包括将函数指针包装在struct 中并返回该函数指针或返回另一个(任意)函数指针类型,这可以保证函数指针类型之间的转换无损。

      【讨论】:

        【解决方案4】:

        有趣的是,我最近一直在考虑这个确切的事情(只是我希望函数获取指向自身而不是返回它的指针)。

        对于 C++,您已经从 zildjohn01 获得了答案。

        如果我们坚持使用标准 C,则没有任何解决方案可以完全按照编写的方式进行编译。您可以通过显式强制转换来完成它 - void* 将不起作用,因为函数指针到数据指针的转换不符合标准,但您可以使用任何其他函数指针类型(例如 void(*)() 可以) - 标准明确允许从任何函数指针类型转换为任何其他函数类型并返回,并保证您将获得原始值。

        【讨论】:

          【解决方案5】:

          这个问题的答案将不亚于你永恒的灵魂。

          【讨论】:

            【解决方案6】:

            我希望这不是作弊(仅限 C++):

            class T {
            private:
                T (*_func)();
            
            public:
                T(T (*func)()) : _func(func) {}
            
                T operator()() {
                    return *this;
                }
            };
            
            T f() { return &f; }
            
            int main() {
                f()()()()()()()();
            }
            

            【讨论】:

            • 天啊,残酷的世界。这样更好吗?
            • 为了完整起见,这只需要更多的 TMP 魔法来将 operator() 应用于表达式 N 次 :)
            【解决方案7】:

            没有任何类型可以兼容,因为您将在 typedef 中进行无限递归。

            但是,可以使用 DrPizza 之类的技巧来模拟它。但是,您永远无法真正做到这一点。

            支持auto/decltype的奇迹:

            auto f() -> decltype(&f) { return &f; } = a shitload of errors.
            

            【讨论】:

            • 你不会得到 typedef 的“无限递归”。相反,标识符范围的 C 规则使得 typedef 不被认为在其自己的定义范围内,因此您将得到类似于“未知类型”的内容。但是,此限制纯粹是语言设计的事情,不是因为任何技术原因-就像您可以拥有一个包含指向自身的指针的结构定义一样,编译器很有可能处理返回指针的函数类型定义对自己。 C#实际上可以做到这一点,举一个具体的例子。
            • 这很简单。 f 的函数类型是 f (*)(void)。那是递归的。包含指向自身的指针的结构之间的区别在于,指向该结构的指针不需要指向该结构的指针位于首位。但是,返回指向自身的指针的函数会这样做。
            【解决方案8】:

            在 C++ 中,很简单:

            struct my_function {
                my_function& operator()() {
                    return *this;
                }
            };
            

            在 C 语言中,您必须强制转换 void*s,这将在任何合理的实现中起作用,但我相信在技术上是未定义的。

            如果两种语言都允许直接递归类型,那就太好了。唉,他们没有。

            【讨论】:

            • 如果我将 T 替换为您的 my_function,我无法看到 T f(){ return &amp;f; } 将如何编译。
            • 不会,也不是故意的。将函数体放在 operator() 重载中。
            • 将 FP 转换为 void* 甚至都不是未定义的,它的格式不正确 - 符合标准的编译器甚至不应该编译它。然而,由于dlsym 的定义,POSIX 实际上需要这样的转换。使用 VC++,它也可以编译,但您会收到警告(“非标准扩展”)。
            猜你喜欢
            • 2016-01-26
            • 2019-05-10
            • 2018-01-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-27
            • 1970-01-01
            • 2020-03-21
            相关资源
            最近更新 更多