std :: tr1 :: function и std :: tr1 :: bind - PullRequest
4 голосов
/ 16 июля 2010

У меня проблема с использованием очень сложной функции C в классе C ++ (перезапись функции C - это , а не опция).Функция C:

typedef void (*integrand) (unsigned ndim, const double* x, void* fdata,
                           unsigned fdim, double* fval);
// This one:
int adapt_integrate(unsigned fdim, integrand f, void* fdata,
                    unsigned dim, const double* xmin, const double* xmax, 
                    unsigned maxEval, double reqAbsError, double reqRelError, 
                            double* val, double* err);

Мне нужно предоставить функцию void типа integrand, и adapt_integrate вычислит n-мерный интеграл.Код в calcTripleIntegral (ниже) работает как отдельная функция, если func - отдельная функция).Я хочу передать (нестатическую!) Функцию-член класса в качестве подынтегрального выражения, поскольку это может быть легко перегружено и т. Д. *

class myIntegrator
{
public:
    double calcTripleIntegral( double x, double Q2, std::tr1::function<integrand> &func ) const
    {
        //...declare val, err, xMin, xMax and input(x,Q2) ...//
        adapt_integrate( 1, func, input,
                         3, xMin, xMax,
                         0, 0, 1e-4,
                         &val, &err);
        return val;
    }
    double integrandF2( unsigned ndim, const double *x, void *, // no matter what's inside
                 unsigned fdim, double *fval) const;            // this qualifies as an integrand if it were not a class member
    double getValue( double x, double Q2 ) const
    {
        std::tr1::function<integrand> func(std::tr1::bind(&myIntegrator::integrandF2, *this);
        return calcTripleIntegral(x,Q2,func);
    }
}

В GCC 4.4.5 (предварительная версия) это дает мне:

ошибка: переменная 'std :: tr1 :: function func' имеет инициализатор, но неполный тип

РЕДАКТИРОВАТЬ : В чем ошибка в моемкод?Теперь я попытался скомпилировать с GCC 4.4, 4.5 и 4.6, все это привело к той же ошибке.Либо ничего не было сделано по этому вопросу, либо я сделал что-то не так / EDIT

Спасибо очень большое!Если я не совсем ясен, я с удовольствием уточню.

PS: Могу ли я обойти это без tr1, используя указатель на функцию, определенную где-то в myIntegrator.cpp?

ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ : хорошо, я ошибался, думая, что TR1 предоставил решение для одной или двух строк.Облом.Я "конвертирую" свои классы в пространства имен и копирую объявления функций.Мне нужен только один базовый класс и один подкласс, который переопределил интерфейс.Указатель на функцию C + класс C ++ = плохие новости для меня.В любом случае, спасибо за ответы, вы показали мне несколько темных углов C ++;)

Ответы [ 7 ]

3 голосов
/ 16 июля 2010

У вас есть три проблемы ... сначала вы хотите std::tr1::function<R (Args..)>, но ваша сводится к std::tr1::function<R (*)(Args...)> - поэтому вам нужно два typedef:

typedef void (integrand) (unsigned ndim, const double *x, void *,
                       unsigned fdim, double *fval);
typedef integrand* integrand_ptr;

... так что первая позволяет вамкомпилируемый function<integrand>.adapt_integrate должно быть исправлено соответствующим образом:

int adapt_integrate(unsigned fdim, integrand_ptr f, ...);

Далее ваш синтаксис bind выключен, он должен быть:

std::tr1::bind(&myIntegrator::integrandF2, *this, _1, _2, _3, _4, _5);

Остается проблема в том, что tr1::function<T> isn 't можно преобразовать в указатель на функцию, поэтому вам придется пройти через функцию-обертку, используя аргумент void* fdata для передачи контекста.Например, что-то вроде:

extern "C" void integrand_helper (unsigned ndim, const double *x, void* data,
                                  unsigned fdim, double *fval)
{
    typedef std::tr1::function<integrand> Functor;
    Functor& f = *static_cast<Functor*>(data);
    f(ndim, x, data, fdim, fval);
}

// ...
adapt_integrate(1, &integrand_helper, &func, ...);

Это, конечно, при условии, что параметр void* передан в функцию, в противном случае он станет уродливым.

С другой стороны, еслиvoid* fdata позволяет передавать контекст, все эти вещи tr1::function не нужны, и вы можете просто пройти через функцию батута - просто пропустите this через аргумент контекста:

extern "C" void integrand_helper (unsigned ndim, const double *x, void* data,
                                  unsigned fdim, double *fval)
{
    static_cast<myIntegrator*>(data)->integrandF2(ndim, ...);
}

// ...
adapt_integrate(1, &integrand_helper, this, ...);
3 голосов
/ 16 июля 2010

Если вы просто пытаетесь передать функцию-член в обратный вызов в стиле c, вы можете сделать это без использования std::t1::bind или std::tr1::function.

class myIntegrator
{
public:
   // getValue is no longer const.  but integrandF2 wasn't changed
   double getValue( double x, double Q2 )
   {
      m_x = x;
      m_Q2 = Q2;

      // these could be members if they need to change
      const double xMin[3] = {0.0};
      const double xMax[3] = {1.0,1.0,1.0};
      const unsigned maxEval = 0;
      double reqAbsError = 0.0;
      double reqRelError = 1e-4;

      double val;

      adapt_integrate( 1, &myIntegrator::fancy_integrand,
                       reinterpret_cast<void*>(this),
                       3, xMin, xMax,
                       maxEval, reqAbsError, reqRelError,
                       &val, &m_err);

      return val;
   }

   double get_error()
   { return m_error; }

private:
   // use m_x and m_Q2 internally
   // I removed the unused void* parameter
   double integrandF2( unsigned ndim, const double *x,
                       unsigned fdim, double *fval) const;

   static double fancy_integrand( unsigned ndim, const double* x, void* this_ptr,
                                  unsigned fdim, double* fval)
   {
      myIntegrator& self = reinterpret_cast<myIntegrator*>(this_ptr);
      self.integrateF2(ndim,x,fdim,fval);
   }

   double m_x
   double m_Q2;
   double m_err;
};
2 голосов
/ 16 июля 2010

Поскольку указатели на функции std::tr1::bind и c-style не уживаются, попробуйте вместо этого. Это будет работать, за исключением того, что myIntegrator::getValue больше не является поточно-ориентированным. Если бы calcTripleIntegral было удалено из интерфейса, это было бы еще проще и не нужно было бы использовать std::tr1::bind или std::tr1::function.

class myIntegrator
{
public:
   double getValue( double x, double Q2 ) const
   {
       return calcTripleIntegral(x,Q2,std::tr1::bind(&Integrator::integrandF2,this));
   }

   double calcTripleIntegral( double x, double Q2, const std::tr1::function<integrand>& func ) const
   {
      assert( s_integrator == NULL );
      s_integrator = this;
      m_integrand = func;

      //...declare val, err, xMin, xMax and input(x,Q2) ...//
      adapt_integrate( 1, &myIntegrator::fancy_integrand, input,
                       3, xMin, xMax,
                       0, 0, 1e-4,
                       &val, &err);

      assert( s_integrator == this);
      s_integrator = NULL;

      return val;
   }
private:
   double integrandF2( unsigned ndim, const double *x, void *,
                unsigned fdim, double *fval) const;

   static double fancy_integrand( unsigned ndim, const double* x, void* input,
                                  unsigned fdim, double* fval)
   {
      s_integrator->integrateF2(ndim,x,input,fdim,fval);
   }

   std::tr1::function<integrand> m_integrand;
   static const myIntegrator* s_integrator;
};
2 голосов
/ 16 июля 2010

Я хочу передать (нестатическую!) Функцию-член класса в качестве подынтегрального выражения ...

Ты не можешь. Если вы будете искать в SO использование функций-членов в качестве обратных вызовов, вы обязательно найдете полезную информацию, в том числе тот факт, что то, что вы пытаетесь сделать, в любом случае, прямой подход невозможен.

Редактировать: Кстати, одна из проблем в вашем коде (конечно, есть и другие, поскольку то, что вы пытаетесь сделать, просто невозможно) состоит в том, что вы передали тип указателя на функцию <>, когда она ожидает это подпись. Шаблон функции реализован примерно так:

template < typename Signature >
struct function;

// for each possible number of arguments:
template < typename R, typename Arg1, typename Arg2 >
struct function<R(Arg1,Arg2)>
{
   ... body ...
};

Как видите, передача указателя на функцию такого рода просто не будет понята компилятором. Он попытается создать экземпляр предварительной декларации и никуда не денется. Это, конечно, означает, что вы получаете ошибку компилятора, но она не решает вашу фундаментальную проблему, а именно то, что вы делаете, никогда не сработает.

В полностью C ++ 0x компиляторе это можно сделать по-другому, но boost :: function и MSVC должны быть такими. Кроме того, версия C ++ 0x будет иметь ту же проблему, с которой вы сталкиваетесь в настоящее время.

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

Предположение, что C-API позволяет передавать независимый от типа тип (в том смысле, что функция C-API не должна знать свой тип, но полагается на функцию обратного вызова, чтобы знать, что ей требуется) контекстного параметра (это обычно имеет место с функциями обратного вызова, в этом случае я подозреваю, что параметр fdata будет чем-то вроде этих строк), передайте объект функции как часть этого параметра контекста.

Затем он должен выглядеть примерно так:

#include <iostream>
#include <tr1/functional>

typedef void (*callback_function_t)(void *input, int arg);

struct data_type { 
  int x;
};

struct context_type {
  std::tr1::function<void(data_type const &, int)> func;
  data_type data;
};

void callback(data_type const&data, int x) {
  std::cout << data.x << ", " << x << std::endl;
}

void callback_relay(void *context, int x) {
  context_type const *ctxt = reinterpret_cast<context_type const*>(context);
  ctxt->func(ctxt->data, x);
}

void call_callback(callback_function_t func, void *context, int x) {
  func(context, x);
}

int main() {
  context_type ctxt = { callback, { 1 } };

  call_callback(callback_relay, &ctxt, 2);
}

Где call_callback - это функция C-API.Таким образом, вы можете назначить для context_type :: func все, что вы хотите, с поддержкой синтаксиса вызова функции, включая выражения std :: tr1 :: bind.Кроме того, хотя (я чувствую себя морально обязанным упомянуть об этом) в стандарте, строго говоря, не определено, что соглашения о вызовах для функций C и C ++ одинаковы, на практике вы можете сделать context_type шаблоном класса, а callback_relay шаблоном функции.чтобы сделать context_type :: data более гибким и передавать все, что вам нравится, таким образом.

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

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

Для вашего примера это сводится к (с заполнителями)

std::tr1::function<integrand> func(std::tr1::bind(&myIntegrator::integrandF2, *this, _1, _2, _3, _4, _5));

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

Для первого вы связываете объект this, для других аргументов вы просто передаете заполнители.

В примечании стороны ваша функция-член возвращает double, а объявление функции возвращает void.

(для справки, я все еще использую старый компилятор с небольшой поддержкой tr1, поэтому у меня есть только опыт bind и function с использованием boost, возможно, что-то немного изменилось для tr1 ...)

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

Это сообщение об ошибке звучит так, как будто вы пропустили включение для одного из типов. По крайней мере, попробуйте дважды проверить ваши integrand и tr1 включает?

...