自指型

发布于 2024-09-05 23:26:39 字数 125 浏览 5 评论 0原文

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

T f(){ return &f; }

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

What type T makes the following code compilable?

T f(){ return &f; }

I'd prefer a C answer, but I marked the question as C and C++ in case there is only an answer using templates.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

请远离我 2024-09-12 23:26:39

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

class T {
private:
    T (*_func)();

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

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

T f() { return &f; }

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

I hope this isn't cheating (C++ only):

class T {
private:
    T (*_func)();

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

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

T f() { return &f; }

int main() {
    f()()()()()()()();
}
仅冇旳回忆 2024-09-12 23:26:39

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

为什么这对于 C 来说是不可能的? (这里有一些推测)

返回 T 的函数的类型是:

T (*)(void) ;

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

对于 struct 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 的答案,在这里)。

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

#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 解决方案,我觉得它远非令人印象深刻,但在我找到它的过程中仍然很有趣。

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

:-)

Interesting problem, but without acceptable C solution, I guess.

Why this is impossible with C ? (some speculation here)

The type of a function returning T is:

T (*)(void) ;

Which expects T to be defined, of course... But then, as T is the type of the function itself, there is a circular dependency.

For a struct T, we could have had:

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

Wouldn't the next notation have been handy?

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

But as there is no way to forward-declare a function, then there is no way use the construct you wrote in your question.

I am no compiler lawyer, but I believe this circular dependency is created only because of typedef notation, not because of C/C++ limitation. After all, function pointers (I'm talking about functions here, not object methods) all have the same size (in the same way struct or class pointers all have the same size).

Studying the C++ solution

As for the C++ solutions, previous answers gave good ones (I'm thinking about zildjohn01's answer, here).

The interesting point is that they are all based upon the fact that structs and classes can be forward declared (and are considered forward-declared in their declaration body) :

#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 ;
}

Which outputs:

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

Inspiration from the C++ solution to reach a C solution

Couldn't we use a similar way in C to achieve comparable results ?

Somehow, yes, but the results are not as sexy as a C++ solution:

#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 ;
}

Which outputs:

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

Conclusion

You'll note that the C++ code is very similar semantically to the C code: Each source will use a structure as a container to the pointer of the function, and then, use the pointer contained to call it again if needed. Of course, the C++ solution uses operator () overload, makes symbols privates, and uses a specific constructor as syntactic sugar.

(This is how I found the C solution: Trying to reproduce the C++ solution "by hand")

I don't believe we could better the C solution's syntactic sugar by using macros, so we're stuck to this C solution, which I find far from impressive, but still being interesting for the time it took me to find it.

After all, searching solutions to bizarre problems is a sure way to learn...

:-)

爱要勇敢去追 2024-09-12 23:26:39

正如 C 常见问题解答问题 1.22 所解释的,这在 C 中是不可能的。解决方法包括包装函数struct 中的指针并返回该指针或返回另一个(任意)函数指针类型,这是可能的,因为函数指针类型之间的转换保证是无损的。

As C FAQ Question 1.22 explains, this is impossible in C. Workarounds include wrapping the function pointer in a struct and returning that or returning another (arbitrary) function pointer type, which is possible as casting between function pointer types is guaranteed to be lossless.

故事灯 2024-09-12 23:26:39

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

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

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

Funny, I've been thinking about this exact thing very recently (only I wanted the function to take pointer to itself rather than return it).

For C++, you already have the answer from zildjohn01.

If we stick to Standard C, there's no solution that would compile exactly as written. You can pull it off with an explicit cast - void* won't work, because function-pointer-to-data-pointer conversion is not standard conformant, but you can use any other function pointer type (e.g. void(*)() will do) - the Standard explicitly permits casting from any function pointer type to any other function type and back, and guarantees that you'll get the original value.

汹涌人海 2024-09-12 23:26:39

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

The answer to this question is going to cost nothing less than your eternal soul.

姐不稀罕 2024-09-12 23:26:39

在 C++ 中,很简单:

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

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

如果两种语言都允许简单的递归类型,那就太好了。可惜,他们没有。

In C++, easy:

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

In C, you'll have to cast void*s, which will work in any reasonable implementation, but is, I believe, technically undefined.

It would be nice if both languages allowed straightforward recursive types. Alas, they do not.

记忆消瘦 2024-09-12 23:26:39

没有类型可以兼容,因为 typedef 中会有无限递归。

然而,可以使用 DrPizza 之类的技巧来模拟它。然而,你永远不可能真正做到这一点。

由 auto/decltype 的奇迹支持:

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

No type makes that compatible, as you would have an infinite recursion in the typedef.

It is, however, possible to do tricks like DrPizza's to simulate it. However, you could never literally do that.

Supported by the miracle of auto/decltype:

auto f() -> decltype(&f) { return &f; } = a shitload of errors.
你穿错了嫁妆 2024-09-12 23:26:39

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

There is no reason that a C compiler back end shouldn't be able to handle that and a lucky front end (aside from the parser and the symbol resolution pass) could as well. But the way C is defined make it imposable to get the data to the later bits of the compiler. OTOH a C compiler that implements typdefs via reference and is non conforming enough to stuff typedefs into the symbol table before it resolves symbols they use would actual swallow it just fine.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文