Самореферентный тип - PullRequest
4 голосов
/ 16 июня 2010

Какой тип T делает следующий код компилируемым?

T f(){ return &f; }

Я бы предпочел ответ C, но я пометил вопрос как C и C ++, если есть только ответ с использованием шаблонов.

Ответы [ 8 ]

9 голосов
/ 16 июня 2010

Надеюсь, это не обман (только C ++):

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

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

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

T f() { return &f; }

int main() {
    f()()()()()()()();
}
9 голосов
/ 16 июня 2010

Интересная проблема, но без приемлемого решения 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 здесь).

Интересно, что все они основаны натот факт, что структуры и классы могут быть заранее объявлены (и считаются заранее объявленными в их теле объявления):

#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, которое, как мне кажется, далеко не впечатляет, но все жебудучи интересным в то время, когда мне потребовалось найти его.

В конце концов, поиск решений странных проблем - верный способ узнать ...

: -)

4 голосов
/ 16 июня 2010

Как C FAQ Вопрос 1.22 объясняет, что это невозможно в C. Обходные пути включают в себя оборачивание указателя функции в struct и возвращение этого или возврат другого (произвольного) типа указателя на функцию, который возможен как приведение между типами указателей на функции гарантированно будет без потерь.

2 голосов
/ 16 июня 2010

Забавно, я совсем недавно думал об этом (только я хотел, чтобы функция взяла указатель на себя, а не вернула его).

Для C ++ у вас уже есть ответ от zildjohn01.

Если мы придерживаемся стандарта C, не существует решения, которое бы компилировалось точно так, как написано. Вы можете выполнить это с явным приведением - void* не будет работать, потому что преобразование указатель на функцию в указатель на данные не является стандартным, но вы можете использовать любой другой тип указателя на функцию (например, void(*)() подойдет ) - Стандарт явно разрешает приведение из любого типа указателя на функцию к любому другому типу функции и обратно и гарантирует, что вы получите исходное значение.

1 голос
/ 16 июня 2010

Ответ на этот вопрос будет стоить не меньше, чем ваша вечная душа.

0 голосов
/ 01 июля 2010

Нет причин, по которым серверная часть компилятора C не могла бы справиться с этим, а также удачный интерфейс (кроме парсера и прохода разрешения символов).Но способ определения C делает невозможным получение данных для более поздних битов компилятора.OTOH компилятор C, который реализует typedefs через ссылку и не соответствует требованиям, достаточным для того, чтобы вставить typedefs в таблицу символов, прежде чем он разрешит символы, которые они используют, фактически проглотит его просто отлично.

0 голосов
/ 16 июня 2010

Ни один тип не делает это совместимым, поскольку у вас будет бесконечная рекурсия в определении типа.

Тем не менее, можно делать трюки типа DrPizza, чтобы имитировать это. Тем не менее, вы никогда не могли бы сделать это буквально.

Поддерживается чудо auto / decltype:

auto f() -> decltype(&f) { return &f; } = a shitload of errors.
0 голосов
/ 16 июня 2010

В C ++ легко:

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

В C вам придется разыгрывать void * s, который будет работать в любой разумной реализации, но, я считаю, технически не определен.

Было бы неплохо, если бы оба языка допускали простые рекурсивные типы. Увы, нет.

...