自我引用类型

4

哪种类型 T 可以使以下代码编译通过?

T f(){ return &f; }

我更希望得到一份C语言的答案,但如果只有使用模板的答案,那么我也将问题标记为C和C++。


6
在C/C++中你可以做任何事情 :-) 但并不总是一个好主意。 - Martin York
我本来想说 void*,但是我及时想起在 C99 标准中不允许将函数指针强制转换为/从 void*。返回 void* 的函数指针可能可行。 - Pascal Cuoq
它必须是一个指向函数的指针,所以我认为Pascal做得对,但我不是C99专家... - Martin Milan
8个回答

9
我希望您不要认为这是作弊(仅限于C++):
class T {
private:
    T (*_func)();

public:
    T(T (*func)()) : _func(func) {}

    T operator()() {
        return *this;
    }
};

T f() { return &f; }

int main() {
    f()()()()()()()();
}

卧槽,这个世界好残酷啊。这样行吗? - zildjohn01
为了完整性,只需要一些更多的TMP魔法来将operator()应用于表达式N次 :) - Pavel Minaev

9
有趣的问题,但我想没有可接受的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++解决方案使用了运算符()重载,使符号变为私有,并使用特定的构造函数作为语法糖。

(这就是我找到C解决方案的方式:试图手动复制C++解决方案)

我不认为我们可以通过使用宏来改进C解决方案的语法糖,因此我们被困在了这个C解决方案中,我认为这远非令人印象深刻,但对于我找到它所花费的时间而言,仍然很有趣。

毕竟,寻找奇怪问题的解决方案是学习的一种可靠方法...

:-)


4

正如C FAQ问题1.22所解释的那样,在C语言中这是不可能的。解决方法包括将函数指针封装在一个struct中并返回该结构,或者返回另一个(任意)函数指针类型,因为在函数指针类型之间进行强制转换是不会有损失的。


2
有趣的是,最近我也在思考这个问题(只是我想让函数接受指向自身的指针而不是返回它)。
对于C++,您已经从zildjohn01那里得到了答案。
如果我们坚持使用标准C,没有解决方案可以与编写的完全相同。您可以通过显式转换来实现它- void * 不起作用,因为函数指针到数据指针的转换不符合标准,但您可以使用任何其他函数指针类型(例如 void(*)())-标准明确允许从任何函数指针类型转换为任何其他函数类型并返回,并保证您将获得原始值。

1

这个问题的答案将不会花费你任何少于永恒灵魂的代价。


0
在C++中,很容易:
struct my_function {
    my_function& operator()() {
        return *this;
    }
};

在 C 中,你必须转换 void*,这在任何合理的实现中都有效,但我认为从技术上讲是未定义的。
如果两种语言都允许直接递归类型就好了。但遗憾的是,它们不允许。

如果我用你的“my_function”替换“T”,我无法看出T f(){ return &f; }会如何编译。 - sbi
它不会,也不应该。将函数体放在operator()重载内部。 - DrPizza
将FP转换为void*甚至不是未定义的,而是不合法的 - 符合标准的编译器甚至不应该编译它。然而,由于dlsym的定义,POSIX实际上需要这样的转换。在VC++中,它也会被编译,但你会收到一个警告(“非标准扩展”)。 - Pavel Minaev

0

没有任何类型可以兼容,因为在typedef中会产生无限递归。

然而,像DrPizza所做的技巧一样模拟它是可能的。但是,你永远不能字面上这样做。

依靠auto/decltype的奇迹得到支持:

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

typedef 不会导致“无限递归”。相反,C 语言的标识符作用域规则是 typedef 在其自身定义中不被视为在作用域内,因此你将得到类似于“未知类型”的错误。然而,这种限制纯粹是一种语言设计问题,而不是任何技术原因 - 就像你可以有一个包含指向自身指针的结构体定义一样,编译器完全可以处理返回指向自身指针的函数类型定义。C# 实际上可以做到这一点,以给出一个具体的例子。 - Pavel Minaev
很简单。函数f的类型是f (*)(void)。这是递归的。包含指向自身指针的结构体与指向结构体的指针不同之处在于,指向结构体的指针不需要将指向结构体的指针放在首位。然而,返回指向自身指针的函数则需要。 - Puppy

0

理论上,C语言编译器的后端(除了解析器和符号解析通道)应该能够处理它,而一个幸运的前端也可以。但是C语言的定义使得将数据传递到编译器后面的部分变得不可能。另一方面,如果一个C语言编译器通过引用实现类型定义,并且足够不符合规范,可以在解析符号之前将typedefs放入符号表中,那么这个问题就可以轻松解决。


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