语法糖:自动创建简单的函数对象
Syntactic sugar: automatically creating simple function objects
我要实现一组类模板和两个特殊变量,_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);
}
- 关于简单C++函数(is_palindrome)的逻辑的问题
- 用于在 C++ 中使用 while 循环查找下一个素数的简单函数
- 简单函数并不总是返回预期的输出
- C++简单函数困境
- 在这个简单函数中从'char'到'const char*'的无效转换
- Github的CodeFactor抱怨简单函数的复杂性
- 带有指针和引用的简单函数
- 无法在语法问题中返回简单函数
- 类方法VS类静态函数VS简单函数-性能方面
- 为什么这个使用未签名乘积的简单函数不起作用
- 如何从C++DLL中的C#简单函数调用
- 为什么tinyxml的FirstAttribute()在简单函数中返回null?
- gcc 5.1 中的链接时间优化是否足以放弃内联简单函数
- 考虑简单函数重载
- 为什么编译器不能为这个简单函数推导模板参数?
- 使用命名空间时,简单函数不起作用
- 如何实现返回二进制数据的简单C函数的单元测试
- 简单函数的麻烦
- c++简单函数返回大值-无论输入是什么,总是相同的值- 4309838
- c++,简单函数,不合理错误