Могу ли я создать предикат, который будет принимать функции и функторы в качестве параметра? - PullRequest
3 голосов
/ 05 августа 2010

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

template <typename type_t>
bool HasTenFoo(const type_t &t) {
  return t.foo >= 10.0;
}

и

template <typename type_t>
bool DoesntHaveTenFoo(const type_t &t) {
  return t.foo < 10.0;
}

Ни один из них не является реальным примером, но они репрезентативны. Я также использую достаточное количество функторов, таких как:

class HasEnoughFoo {
public:
  HasEnoughFoo (double bar) { this->bar = bar; }
  template<typename type_t>
  bool operator()(const type_t &t) const { return t.foo >= bar; }
private:
  double bar;
};

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

/* -- Returns the opposite of some other predicate -------------------------- */

template<typename predicate_t>
class Not {
public:
  template <typename predicate_t>
  Not(predicate_t *p) { predicate = p; }

  template <typename type_t>
  bool operator()(const type_t &t) const {
    return !(*predicate)(t);
  }

private:
  predicate_t *predicate;
};

Я бы назвал это чем-то вроде:

new_list = old_list.subset(Not<HasEnoughFoo>(&HasEnoughFoo(10.0));

или

new_list = old_list.subset(Not<HasTenFoo>(&HasTenFoo));

Кажется, это хорошо работает, когда predicate_t является функтором, подобным HasEnoughFoo, но не работает, когда predicate_t относится к обычной функции, такой как HasTenFoo.

Visual Studio жалуется, что 'HasTenFoo' is not a valid template type argument for parameter 'predicate_t'. Есть ли способ написать предикат Not (), который будет работать с функторами и функциями, или я обречен на написание десятков предикатов и их инверсий?

Ответы [ 6 ]

5 голосов
/ 05 августа 2010

Вот пример вашего кода, созданного для работы (я удалил элемент foo, чтобы он работал только с двойными числами).

template <typename type_t>
bool HasTenFoo(const type_t &t) {
  return t >= 10.0;
}

class HasEnoughFoo {
public:
  HasEnoughFoo (double bar) { this->bar = bar; }
  template<typename type_t>
  bool operator()(const type_t &t) const { return t >= bar; }
private:
  double bar;
};


template<typename predicate_t>
class Not {
public:
  Not(predicate_t p): predicate(p) { }

  template <typename type_t>
  bool operator()(const type_t &t) const {
    return !predicate(t);
  }

private:
  predicate_t predicate;
};

template <class predicate_type>
Not<predicate_type> Negate(predicate_type p)
{
    return p;
}

#include <iostream>
int main()
{
    std::cout << Negate(HasTenFoo<double>)(11.0) << '\n';
    std::cout << Negate(HasEnoughFoo(13.0))(11.0) << '\n';
}

Некоторые важные замечания:

Not'sКонструктор использует список инициализации.Это устраняет требование, чтобы у типа предиката был конструктор по умолчанию (которого у HasEnoughFoo нет).

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

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

3 голосов
/ 05 августа 2010

Есть встроенный предикат not1 . Он принимает предикат и отрицает его. Это будет работать с любым предикатом, производным от unary_predicate. Это избавит вас от необходимости использовать предикат, созданный вручную.

Некоторая комбинация этого и ptr_fun может достичь того, что вы хотите.

Редактировать, Нечто подобное может сработать (предупреждение, полностью не проверено, даже не уверен, что это скомпилируется)

int factorial (int x) {
    ....
}
std::transform (d.begin (), d.end (), v.begin (), std::ptr_fun (factorial));
std::transform (d.begin (), d.end (), v.begin (), not1(std::ptr_fun (factorial)));
3 голосов
/ 05 августа 2010

У вас две большие проблемы.

Первая проблема заключается в том, что HasTenFoo является функцией шаблона. Шаблоны на самом деле не существуют; Вы не можете взять адрес одного, так как он не существует. Однако существуют шаблоны шаблонов. &HasTenFoo является незаконным, &HasTenFoo<Bar> является законным. HasTenFoo<Bar> относится к конкретному экземпляру шаблона HasTenFoo.

Вторая проблема заключается в том, что параметром шаблона класса Not должен быть тип функции, которую вы передаете ему. Если вы задаете HasTenFoo<Bar>, параметр шаблона должен быть bool(*)(const Bar&).

Итак, правильная версия будет

Not<bool(*)(const Bar&)>(&HasTenFoo<Bar>)

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

0 голосов
/ 05 августа 2010

Функции являются функторами , что означает, что если вы все делаете правильно, ваш шаблон Not должен работать как есть.

Во-первых, в объявлении шаблона Not есть очевидная ошибка - какой-то странный дополнительный template вокруг конструктора.Вот как это должно было выглядеть

template<typename predicate_t>
class Not {
public:
  Not(predicate_t *p) { predicate = p; }

  template <typename type_t>
  bool operator()(const type_t &t) const {
    return !(*predicate)(t);
  }

private:
  predicate_t *predicate;
};

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

Теперь, чтобы использовать это с вашим HasTenFoo, мы просто сделали бы это следующим образом

new_list = old_list.subset(Not<bool(const LIST_ELEMENT&)>(&HasTenFoo));

или, чтобы сделать егонемного более читабельно

typedef bool OrdinaryFuncPredicate(const LIST_ELEMENT&);
new_list = old_list.subset(Not<OrdinaryFuncPredicate>(&HasTenFoo));

Обратите внимание на тип, используемый в качестве аргумента шаблона для Not шаблона.То, что вы использовали в своем примере, не имело смысла (это было значение, а не тип, что именно то, что вам явно сказал компилятор).Поскольку вы используете функцию в качестве предиката, вы должны указать тип функции в качестве аргумента шаблона.Этот тип функции должен принимать тип элемента списка в качестве аргумента функции.Из вашего исходного поста не ясно, какой тип элемента списка, поэтому я просто использовал LIST_ELEMENT name в качестве замены.

0 голосов
/ 05 августа 2010

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

template<typename type_t> class HasEnoughFoo {
public:
  HasEnoughFoo (double bar) { this->bar = bar; }
  bool operator()(const type_t &t) const { return t.foo >= bar; }
private:
  double bar;
};

template<typename predicate_t, typename type_t>
class Not {
public:
  Not(const predicate_t& p) { predicate = p; }

  bool operator()(const type_t &t) const {
    return !(predicate(t));
  }

private:
  predicate_t predicate;
};

Теперь этот функционал может принимать указатели на функции или функциональные объекты, которые принимают правильные аргументы.

typedef type_t MyCustomType;
new_list = old_list.subset(Not<HasEnoughFoo<type_t>>(&HasEnoughFoo<type_t>(10.0));
new_list = old_list.subset(Not<decltype(HasTenFoo<&type_t)>>(&HasTenFoo<type_t>));

По существу:
Поместите операторы шаблона в тип класса функции, а НЕ в оператор или конструкторы.
Функции ДОЛЖНЫ иметь свои полные аргументы шаблона, чтобы их адрес был взят. Как еще компилятор узнает, какой адрес функции вы хотите?

Извините, что использовал decltype, я не удосужился напечатать сигнатуру функции. Введите фактическую подпись здесь.

0 голосов
/ 05 августа 2010

Вы можете использовать boost :: lambda . Это позволяет вам создавать предикаты и другие функторы, используя небольшой код.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...