2015-10-26 6 views
7

более чем в общем случае, у меня есть очень конкретный пример: в GSL (GNU Scientific Library) используется основной тип функции (для выполнения интеграции, поиска корней, ...) является gsl_function, которые имеют атрибут function, тип которого double(*)(double, void *)Создайте функцию из другого

Say Я хочу, чтобы создать gsl_function из double a_squared(double a) {return a*a};. Тип a__squared «s является double(*)(double) Я хотел бы создать convert функцию принятия в аргументе (double(*)(double) f) и возвращает объект типа double(*)(double, void *), который удовлетворял бы convert(f)(double a, NULL) == f(a)

Но после некоторых исследований, кажется, что я не могу определить другую функцию в моем convert функция. Как действовать ?

+0

Почему это помечено 'C++'? GSL является библиотекой C и требует указателя функции типа C. Поэтому любое решение, основанное на функциональности C++ (например, 'std :: bind'), не выполняется. – Walter

+0

Функциональность C++ не сбой, если вы используете небольшую обертку @Walter - и которая включает lambda и 'std :: bind'. См. Мой ответ –

ответ

4

Необходимость передачи необработанного указателя функции в API GSL значительно ограничивает ваши возможности - вы не можете использовать что-либо на основе std::function, потому что нет способа получить указатель на функцию из std::function (и это исключает использование lambdas с помощью захватов , который предложил бы опрятное решение).

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

typedef double gsl_function_type(double, void*); // typedef to make things a bit more readable... 

// static class to wrap single-parameter function in GSL-compatible interface 
// this really just serves as a namespace - there are no non-static members, 
// but using a class lets us keep the details private 
class Convert 
{ 
    Convert() = delete; // don't allow construction of this class  

    // pointer to the function to be invoked 
    static double (*m_target)(double); 

    // this is the function we'll actually pass to GSL - it has the required signature 
    static double target(double x, void*) { 
     return m_target(x); // invoke the currently wrapped function 
    } 

public: 
    // here's your "convert" function 
    static gsl_function_type* convert(double (*fn)(double)) { 
     m_target = fn; 
     return ⌖ 
    } 
}; 

Там есть живой пример здесь: http://coliru.stacked-crooked.com/a/8accb5db47a0c51d

+1

, скрывающий 'm_target' как член класса' static', не исключает того, что для этого требуется глобальная переменная. Более того, это позволяет одновременно конвертировать одну функцию (т. Е. Она терпит неудачу, например, если вы хотите вычислить численный интеграл функции, значение которой требует корневой поиск, * и * вы хотите использовать этот механизм для обоих). – Walter

+0

@Walter, связанная с оболочкой связь не имеет этого ограничения (по одной функции за раз). –

1

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

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

// from some gsl header: 
extern "C" { 
    typedef double gsl_function(double, void*); 
    // calls func(arg,data_passed_to_func) 
    double gsl_api_function(gsl_function*func, void*data_passed_to_func); 
} 

// in your source code 
double(*target_func)(double);   // global variable can be hidden in some namespace 
extern "C" { 
    double funtion_calling_target(double, void*) 
} 
double funtion_calling_target(double arg, void*) 
{ 
    return target_func(arg); 
} 
bool test(double x, double(*func)(double)) 
{ 
    target_func = func; 
    return x < gsl_api_function(function_calling_target,0); 
} 

(пряча target_func, как статический член какого-либо класса, так как в ответе Аткинса по-прежнему требуется глобальная переменная). Это работает, но плохо, так как 1) этот механизм требует глобальной переменной и 2) позволяет использовать только одну целевую функцию в любое время (что может быть трудно гарантировать).

(Рекомендован) Однако вы можете определить специальную функцию, которая принимает в качестве аргумента другой указатель функции и передает его как элемент данных. На самом деле идея дизайна gsl_function: void* может указывать на любые вспомогательные данные, которые могут потребоваться этой функции. Такие данные могут быть другой функцией.

// your header 
extern "C" { 
    double function_of_double(double, void*); 
} 
inline double function_of_double(double arg, void*func) 
{ 
    typedef double(*func_of_double)(double); 
    return reinterpret_cast<func_of_double>(func)(arg); 
} 

// your application 
bool test(double x, double(*func)(double)) 
{ 
    return x < gsl_api_function(function_of_double, (void*)(func)); 
} 

Это не требует глобальной переменной и работает с множеством различных одновременных функций, которые вы хотите. Конечно, здесь вы возитесь с void*, тем, что каждый здравомыслящий программист на С ++ терпит неудачу, но тогда вы используете ужасную библиотеку C, основанную на манипуляциях void*.

1

Думаю, что я добавлю свои попытки на основе лямбда.

Это прекрасно работает в принципе:

// function we want to pass to GSL 
double a_squared(double a) { return a*a; } 

typedef double gsl_function_type(double, void*); // convenient typedef 

// lambda wrapping a_squared in the required interface: we can pass f directly to GSL 
gsl_function_type* f = [](double x, void*) { return a_squared(x); }; 

Но мы действительно хотели бы написать метод, чтобы применить это к той или иной функции. Что-то вроде этого:

gsl_function_type* convert(double (*fn)(double)) 
{ 
    // The lambda has to capture the function pointer, fn. 
    return [fn](double x, void*) { return fn(x); }; 
} 

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

Таким образом, единственный способ, мне удалось получить эту работу является использование препроцессора макрос:

#define convert(f) [](double x, void*) { return f(x); } 

Это то позволяет мне написать что-то вроде этого:

#include <iostream> 
using namespace std; 

typedef double gsl_function_type(double, void*); // convenient typedef 

// example GSL function call 
double some_gsl_function(gsl_function_type* function) 
{ 
    return function(5.0, nullptr); 
} 

// function we want to pass to GSL 
double a_squared(double a) { return a*a; } 

// macro to define an inline lambda wrapping f(double) in GSL signature 
#define convert(f) [](double x, void*) { return f(x); } 

int main() 
{ 
    cout << some_gsl_function(convert(a_squared)) << endl; 
} 

лично, так как мне не нравится использование макросов, я бы предпочел это по моему другому предложению. В частности, он решает проблемы, которые @Walter указал на эту идею.

+1

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

1

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

Если вы используете оболочку, описанную here, то вы можете преобразовать любого C++ лямбду для gsl_functions в двух простых линиях

// Example 
gsl_function_pp Fp([&](double x){return a_squared(x);}); 
gsl_function *F = static_cast<gsl_function*>(&Fp); 

Это решает любые проблемы, связанные с преобразованием. Вы также можете использовать std :: bind и любые std :: функции.

+0

Да, мне это нравится. – atkins

Смежные вопросы