Могу ли я объявить функцию, которая может принимать указатель на себя в качестве аргумента? - PullRequest
11 голосов
/ 03 октября 2009

Читая вопрос в stackoverflow, я задался вопросом, возможно ли объявить функцию, которая принимает указатель на себя. То есть сделать такое объявление foo, для которого было бы правильным следующее:

foo(foo);

Простейшей идеей является приведение к другому указателю функции (не может быть приведено к void*, поскольку оно может быть меньше), поэтому объявление функции выглядит следующим образом:

void foo(void (*)());

Хотя это нормально в C (и будет работать с приведением типов в C ++), мне интересно, может ли это быть сделано без такого «переосмысления» преобразования или потери информации о типах.

Другими словами, я хочу следующее объявление:

void foo( void (*)( void (*)( void (*) ( ... ))));

но, конечно, неограниченные декларации невозможны. Наивный typedef ing тоже не помогает.

Шаблоны C ++ приветствуются, даже если они заставляют вызывающий код (foo(foo)) выглядеть немного менее лаконичным, но все же конечным .

Ответы в стиле C, показывающие, как можно отбрасывать информацию о типах без приведения, или другие подобные трюки, конечно, интересны, но не будут приняты.

Ответы [ 8 ]

5 голосов
/ 07 октября 2009

Еще один подвох.

void Foo( ... )
{
}

int main()
{
 Foo( Foo );
}

Выше программа будет компилироваться без каких-либо ошибок. Но это не рекурсивно. Следующая измененная функция является рекурсивной версией с ограничителем.

#define RECURSIVE_DEPTH (5)

typedef void ( *FooType )( int, ... );

void Foo( int Depth, ... )
{
 void ( *This )( int, ... );

 va_list Arguments;

 va_start( Arguments, Depth );

 if( Depth )
 {
  This = va_arg( Arguments, FooType );

  This( Depth - 1, This );
 }

 va_end ( Arguments );  
}

int main()
{
 Foo( RECURSIVE_DEPTH, Foo );
}
5 голосов
/ 03 октября 2009

Видимо нет - см. в этой теме . Тип, требуемый здесь, всегда будет бесконечным.

3 голосов
/ 05 октября 2009

ДА

Это вариант «Можете ли вы написать функцию, которая возвращает указатель на себя?» , за исключением того, что в вашем случае тип функции рекурсивно отображается как аргумент, а не как тип возвращаемого значения. Ответ Херба Саттерса можно использовать повторно: заключить указатель в прокси-класс, объявленный заранее.

2 голосов
/ 04 октября 2009

Связанная проблема - возврат указателя функции того же типа. Он появляется при реализации конечных автоматов, поэтому у него есть собственная запись в C FAQ .

Те же обходные пути могут быть применены к вашей проблеме.

2 голосов
/ 03 октября 2009

Как правило, я согласен с Дарио - сделать это на уровне типов невозможно.

Но вы можете использовать классы («шаблон стратегии»):

class A {
  void evil(A a) {       // a pointer to A is ok too
  }
};

Вы даже можете добавить оператор ():

  void operator()(A a) { return evil(a); }

Обычно такие вещи лучше делать на языках FP. Версия на Haskell проста:

data Evil = Evil (Evil -> Integer)

При этом используется оболочка (Evil), соответствующая использованию класса.

От опрашивающего : этому ответу не хватало способности передать несколько различных функций в качестве аргумента одной из них. Эту проблему можно решить либо путем создания evil() виртуального, либо путем явного сохранения в объекте указателя функции, который его реализует (что в основном совпадает).

С этим разъяснением ответ достаточно хорош для принятия.

1 голос
/ 04 октября 2009

Я не верю, что у вас может быть функция, которая может принимать себя в качестве аргумента в C ++ без каких-либо хитростей, таких как помещение вашей функции в класс и ее использование в качестве аргумента этого класса.

Еще один способ, который будет возможен после появления нового стандарта C ++, - это использовать лямбда-выражения и иметь сам лямбда-захват. Я думаю, что это будет примерно так:

auto recursive_lambda = [&recursive_lambda] { recursive_lambda(); };

Имейте в виду, что такое утверждение полностью не проверено в любом компиляторе, который поддерживает лямбды-выражения. Ваш пробег может отличаться.

0 голосов
/ 03 октября 2009

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

0 голосов
/ 03 октября 2009

Это совершенно правильное использование void *.

typedef void T(void *);

void f(T *probably_me)
{
  (*probably_me)(f);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...