wh1*_*t1k 4 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);
Run Code Online (Sandbox Code Playgroud)
我想_1*5也应该产生一元函数,以及_1/5等.
现在我有非常有模板和模板元编程经验不多,所以我甚至不知道从哪里开始,什么我的类模板结构应该像.我特别困惑,我不知道在我的模板类,我将不得不写这一切的实现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);
Run Code Online (Sandbox Code Playgroud)
您显然希望允许以下用例:
std::for_each(vec.cbegin(), vec.cend(), _1);
Run Code Online (Sandbox Code Playgroud)
但是如何允许这个呢?首先,您需要定义_1,然后您需要为函数调用操作符实现"任何事情"重载_1.
该升压LAMBDA并提高绑定定义占位符对象的方式_1,_2......是让他们有一个虚拟的类型.例如,_1对象可能具有以下类型placeholder1_t:
struct placeholder1_t { };
placeholder1_t _1;
struct placeholder2_t { };
placeholder2_t _2;
Run Code Online (Sandbox Code Playgroud)
这种"虚拟类型"经常被非正式地称为标签类型.有许多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;
}
};
Run Code Online (Sandbox Code Playgroud)
而已!我们最简单的用例现在将编译并运行:
std::for_each(vec.cbegin(), vec.cend(), _1);
Run Code Online (Sandbox Code Playgroud)
当然,它基本上等于无操作.
我们现在继续努力吧_1 + 5.那个表达应该做什么?它应该返回一个一元函数对象,当使用参数(某种未知类型)调用时,结果是该参数加上5.使这更通用,表达式是一元函数对象 + 对象.返回的对象本身就是一个一元的功能对象.
需要定义返回对象的类型.它将是一个带有两个模板类型参数的模板:一元函数类型和要添加到一元函数结果的对象类型:
template <typename UnaryFnT, typename ObjT>
struct unary_plus_object_partfn_t;
Run Code Online (Sandbox Code Playgroud)
"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)
{
}
};
Run Code Online (Sandbox Code Playgroud)
好的.函数调用运算符也需要重载以允许任何参数.我们将使用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;
}
};
Run Code Online (Sandbox Code Playgroud)
它开始变得复杂,但这段代码没有任何意外.它基本上说函数调用操作符被重载以接受几乎任何参数.然后它将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);
}
Run Code Online (Sandbox Code Playgroud)
我们现在可以编译并运行第二个用例:
std::transform(vec.cbegin(), vec.cend(), std::ostream_iterator<int>(std::cout, " "), _1 + 5);
std::cout << std::endl;
Run Code Online (Sandbox Code Playgroud)
哪个输出:
9 -3 6 5 12
这基本上就是解决问题所需要做的一切.考虑如何编写自定义函数类型,其实例可以由运算符的重载返回.
编辑:通过使用pass-by-reference改进函数调用操作符的重载.
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;
Run Code Online (Sandbox Code Playgroud)
此模板的实例化实例将需要存储对类型对象的引用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)
{
}
};
Run Code Online (Sandbox Code Playgroud)
像以前一样添加函数调用运算符的重载以及插入运算符的重载<<.
在这种情况下std::cout << _1,返回的对象将具有类型ref_insert_unary_partfn_t<std::basic_ostream<char>, placeholder1_t>.