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

Syntactic sugar: automatically creating simple function objects

本文关键字:简单 函数 对象 创建 语法      更新时间:2023-10-16

我要实现一组类模板和两个特殊变量,_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等。

  • 不允许升压
  • 不允许lambda

现在我有非常很少的经验与模板和模板元编程,所以我甚至不知道从哪里开始,我的类模板的结构应该是什么样子。我特别困惑,因为我不知道是否在我的类模板中,我将不得不为所有这些operator=, operator>>, operator+, ...-, ...*, .../分别编写实现-或者有更通用的方法来做到这一点。

如果能给出一个实现这些运算符的例子,我将特别感激;模板对我来说仍然是一团糟。

好吧!这确实是一个棘手的家庭作业问题!但是,这也是一个很好的问题,值得研究和学习。

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

例如,假设您有以下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类型的函数调用操作符的"anything goes"重载。

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-function -object + object。返回的对象本身是一元函数对象。

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

template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t;

"partfn"是一个函数类型,表示二进制+运算符的部分应用。此类型的实例需要一元函数对象(类型为UnaryFnT)和另一个对象(类型为ObjT)的副本:

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)
    {
    }
};

好吧。函数调用操作符也需要重载以允许任何参数。我们将使用c++ 11 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添加到结果中。返回类型为m_fn(arg) + m_obj的decltype。

既然定义了类型,就可以在左侧编写接受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);
}

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

std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5);
std::cout << std::endl;
输出:

<>之前9 -3 6 5 12之前

这基本上就是你解决这个问题所需要做的一切。考虑一下如何编写自定义函数类型,它的实例可以通过重载操作符返回。

EDIT:通过引用传递改进了函数调用操作符的重载。

EDIT2:在某些情况下,有必要存储对对象的引用而不是对象的副本。例如,为了适应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,返回的对象类型为ref_insert_unary_partfn_t<std::basic_ostream<char>, placeholder1_t>

一个简单的例子:

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);
}