语法糖:自动创建简单的函数对象

时间:2011-12-09 15:24:41

标签: c++ templates template-meta-programming

我要实现一组类模板和两个特殊变量_1_2

他们应该制作以下法律代码:

// Sort ascending
std::sort(a, a+5, _1 > _2);

// Output to a stream
std::for_each(a, a+5, std::cout << _1 << " ");

// Assign 100 to each element
std::for_each(a, a+5, _1 = 100);

// Print elements increased by five 5
std::transform(a, a+5, std::ostream_iterator<int>(std::cout, " "), _1 + 5);

我认为_1 * 5也应该产生一元函数,以及_1 / 5等。

  • 不允许提升
  • 禁止使用lambdas

现在我对模板和模板元编程的经验很少非常,所以我甚至不知道从哪里开始以及我的类模板的结构应该是什么样子。我特别困惑,因为我不知道在我的类模板中是否必须为所有这些operator=operator>>operator+...-,{{ 1}},...*分开 - 或者有更通用的方法来执行此操作。

我将特别感谢这些操作符的实现示例的答案;模板对我来说似乎仍然很糟糕。

2 个答案:

答案 0 :(得分:5)

嘛!确实,这是一个棘手的作业问题!但是,这也是一个非常好的问题,可以继续学习和学习。

我认为,回答这个问题的最佳方法是从简单的用例开始,逐步构建解决方案。

例如,假设您使用以下std::vector<int>

std::vector<int> vec;
vec.push_back(4);
vec.push_back(-8);
vec.push_back(1);
vec.push_back(0);
vec.push_back(7);

您显然希望允许以下用例:

std::for_each(vec.cbegin(), vec.cend(), _1);

但如何允许这个?首先,您需要定义_1,然后您需要为_1类型的函数调用运算符实现“任何事情”重载。

Boost Lambda和Boost Bind定义占位符对象_1_2,...的方式是使它们具有虚拟类型。例如,_1对象可能具有类型placeholder1_t

struct placeholder1_t { };
placeholder1_t _1;

struct placeholder2_t { };
placeholder2_t _2;

这种“虚拟类型”经常被非正式地称为标签类型。有许多C ++库,实际上是依赖于标记类型的STL(例如std::nothrow_t)。它们用于选择“正确”的函数重载来执行。本质上,创建具有标记类型的虚拟对象,并将这些对象传递给函数。该函数不以任何方式使用虚拟对象(事实上,大多数情况下甚至没有为它指定参数名称),但是由于存在该额外参数,编译器能够选择正确的重载来调用

让我们通过添加函数调用运算符的重载来扩展placeholder1_t的定义。请记住,我们希望它接受任何内容,因此函数调用操作符的重载本身将在参数类型上进行模板化:

struct placeholder1_t
{
    template <typename ArgT>
    ArgT& operator()(ArgT& arg) const {
        return arg;
    }

    template <typename ArgT>
    const ArgT& operator()(const ArgT& arg) const {
        return arg;
    }
};

就是这样!我们最简单的用例现在将编译并运行:

std::for_each(vec.cbegin(), vec.cend(), _1);

当然,它基本上等于无操作。

现在让我们开始_1 + 5。该表达式应该做什么?它应该返回一个一元函数对象,当使用参数(某种未知类型)调用时,结果是该参数加上5.使这更通用,表达式是 unary-functional-object { {1}} 对象。返回的对象本身就是一个单一的功能对象。

需要定义返回对象的类型。它将是一个带有两个模板类型参数的模板:一元函数类型和要添加到一元函数结果中的对象类型:

+

“partfn”是指表示二进制template <typename UnaryFnT, typename ObjT> struct unary_plus_object_partfn_t; 运算符的部分应用的函数类型。此类型的实例需要一元函数对象(具有类型+)和另一个对象(具有类型UnaryFnT)的副本:

ObjT

好。函数调用运算符也需要重载以允许任何参数。我们将使用C ++ 11 template <typename UnaryFnT, typename ObjT> struct unary_plus_object_partfn_t { UnaryFnT m_fn; ObjT m_obj; unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj) : m_fn(fn), m_obj(obj) { } }; 功能来引用表达式的类型,因为我们事先不知道它是什么:

decltype

它开始变得复杂,但这段代码没有任何意外。它基本上说函数调用操作符被重载以接受几乎任何参数。然后它将在参数上调用template <typename UnaryFnT, typename ObjT> struct unary_plus_object_partfn_t { UnaryFnT m_fn; ObjT m_obj; unary_plus_object_partfn_t(UnaryFnT fn, ObjT obj) : m_fn(fn), m_obj(obj) { } template <typename ArgT> auto operator()(ArgT& arg) const -> decltype(m_fn(arg) + m_obj) { return m_fn(arg) + m_obj; } template <typename ArgT> auto operator()(const ArgT& arg) const -> decltype(m_fn(arg) + m_obj) { return m_fn(arg) + m_obj; } }; (一元函数对象)并将m_fn添加到结果中。返回类型是m_obj的decltype。

现在定义了类型,我们可以编写二进制运算符m_fn(arg) + m_obj的重载,接受左侧类型为+的对象:

placeholder1_t

我们现在可以编译并运行第二个用例:

template <typename ObjT>
inline unary_plus_object_partfn_t<placeholder1_t, ObjT> operator+(const placeholder1_t& fn, ObjT obj)
{
    return unary_plus_object_partfn_t<placeholder1_t, ObjT>(fn, obj);
}

输出:

9 -3 6 5 12

这基本上是您解决问题所需要做的所有事情。想想你如何编写自定义函数类型,其实例可以由运算符的重载返回。

编辑:通过使用pass-by-reference改进了函数调用运算符的重载。

EDIT2:在某些情况下,有必要存储对象的引用而不是对象的副本。例如,为了容纳std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5); std::cout << std::endl; ,您需要在结果函数对象中存储对std::cout << _1的引用,因为std::cout复制构造函数是私有的,并且不可能复制构造对象任何来自std::ios_base的类,包括std::ios_base

要允许std::ostream,您可能需要编写std::cout << _1模板。这样的模板,就像上面ref_insert_unary_partfn_t的例子一样,将模仿对象类型和一元函数类型:

unary_plus_object_partfn_t

此模板的实例化实例需要存储对类型为template <typename ObjT, typename UnaryFnT> struct ref_insert_unary_partfn_t; 的对象的引用以及类型为ObjT的一元函数对象的副本:

UnaryFnT

像以前一样添加函数调用运算符的重载以及插入运算符template <typename ObjT, typename UnaryFnT> struct ref_insert_unary_partfn_t { ObjT& m_ref; UnaryFnT m_fn; ref_insert_unary_partfn_t(ObjT& ref, UnaryFnT fn) : m_ref(ref), m_fn(fn) { } }; 的重载。

对于<<,返回的对象的类型为std::cout << _1

答案 1 :(得分:2)

一个简单的例子:

template <typename T>
class Parameter
{
};

template <typename T>
struct Ascending
{
    bool operator()(T left, T right)
    {
        return left < right;
    }
};

template <typename T>
Ascending<T> operator > (Parameter<T> p1, Parameter<T> p2)
{
    return Ascending<T>();
}

int main()
{

    std::vector<int> vec;
    vec.push_back(3);
    vec.push_back(6);
    vec.push_back(7);
    vec.push_back(2);
    vec.push_back(7);

    std::vector<int>::iterator a = vec.begin();

    Parameter<int> _1;
    Parameter<int> _2;

    std::sort(a, a+4, _1 > _2);
}