我可以创建一个接受函数和函子作为参数的谓词吗?

时间:2010-08-05 15:22:33

标签: c++ templates

我正在研究C ++中的一个问题,该问题涉及大量数据的大量子集和转换操作。为此,我创建了一个map函数和list comprehensions之类的东西。我发现我写的一堆谓词也有反转,所以我需要写:

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_tHasEnoughFoo这样的仿函数时似乎运行良好,但predicate_t引用HasTenFoo这样的常规函数​​时失败。

Visual Studio抱怨'HasTenFoo' is not a valid template type argument for parameter 'predicate_t'。有没有办法编写一个可以与仿函数和函数一起使用的Not()谓词,或者我注定要编写几十个谓词及其反转?

6 个答案:

答案 0 :(得分:5)

以下是您的代码工作示例(我删除了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的构造函数使用初始化列表。这消除了谓词类型具有默认构造函数(HasEnoughFoo没有)的要求。

你绝对不想搞乱指向谓词的指针。函数对象应该是轻量级的对象,可以毫无顾虑地复制。

因为Not是一个模板类,具有可能复杂的模板参数,但您通常只是将它用作临时(作为获取谓词的函数的未命名参数),添加一个推导出复杂类型的模板函数为你(在整个标准库中使用的技巧) - 这里Negate

答案 1 :(得分:3)

你有两大问题。

第一个问题是HasTenFoo是模板函数。模板实际上并不存在;你不能取一个地址,因为它不存在。但是,确实存在模板的实例化。 &HasTenFoo是非法的,&HasTenFoo<Bar>是合法的。 HasTenFoo<Bar>是指HasTenFoo模板的特定实例。

第二个问题是Not类的模板参数必须是您传递它的函数的类型。如果您提供HasTenFoo<Bar>,则模板参数应为bool(*)(const Bar&)

所以,正确的版本将是

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

请注意,要使其适用于函数和仿函数,您必须存储对象/函数的副本,而不是指向它们的指针。这是首选方法;所有带有仿函数的标准库函数都保留了内部副本。

答案 2 :(得分:3)

内置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 :(得分:0)

您可以使用boost::lambda。它允许您使用少量代码构造谓词和其他函子。

答案 4 :(得分:0)

你做错了。函数对象惯用法接受函数指针或函数对象。另外,您不正确地获取了模板函数的地址。您没有指定类型。此外,据我所知,实例化像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;
};

现在这个functionoid可以接受接受正确参数的函数指针或函数对象。

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,我不能打扰输入函数签名。在此插入实际签名。

答案 5 :(得分:0)

函数仿函数,这意味着如果您正确执行所有操作,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元素类型作为函数参数。从原始帖子中不清楚列表元素类型是什么,所以我只使用LIST_ELEMENT名称作为替代。