命名运算符
你可以使用由标准 C++运算符引用的命名运算符来扩展 C++。
首先,我们从十几个库开始:
namespace named_operator {
template<class D>struct make_operator{constexpr make_operator(){}};
template<class T, char, class O> struct half_apply { T&& lhs; };
template<class Lhs, class Op>
half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op, class Rhs>
auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
-> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
{
return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
}
这还没有做任何事情。
首先,附加向量
namespace my_ns {
struct append_t : named_operator::make_operator<append_t> {};
constexpr append_t append{};
template<class T, class A0, class A1>
std::vector<T, A0> named_invoke( std::vector<T, A0> lhs, append_t, std::vector<T, A1> const& rhs ) {
lhs.insert( lhs.end(), rhs.begin(), rhs.end() );
return std::move(lhs);
}
}
using my_ns::append;
std::vector<int> a {1,2,3};
std::vector<int> b {4,5,6};
auto c = a *append* b;
这里的核心是我们定义 append_t:named_operator::make_operator<append_t>
类型的 append
对象。
然后我们在右侧和左侧为我们想要的类型重载 named_invoke(lhs,append_t,rhs)。
该库重载 lhs*append_t
,返回一个临时的 half_apply
对象。它也会超过 half_apply*rhs
来调用 named_invoke( lhs, append_t, rhs )
。
我们只需要创建正确的 append_t
令牌并执行适当签名的 ADL 友好的 named_invoke
,并且所有内容都可以连接并运行。
对于更复杂的示例,假设你希望对 std::array 的元素进行逐元素乘法运算:
template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
return [](auto&& f) {
return f( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto indexer() { return indexer( std::make_index_sequence<N>{} ); }
namespace my_ns {
struct e_times_t : named_operator::make_operator<e_times_t> {};
constexpr e_times_t e_times{};
template<class L, class R, std::size_t N,
class Out=std::decay_t<decltype( std::declval<L const&>()*std::declval<R const&>() )>
>
std::array<Out, N> named_invoke( std::array<L, N> const& lhs, e_times_t, std::array<R, N> const& rhs ) {
using result_type = std::array<Out, N>;
auto index_over_N = indexer<N>();
return index_over_N([&](auto...is)->result_type {
return {{
(lhs[is] * rhs[is])...
}};
});
}
}
实例 。
如果你决定长度不匹配时该怎么做,则可以扩展此元素数组代码以处理元组或对或 C 样式数组,甚至可变长度容器。
你也可以使用元素运算符类型并获取 lhs *element_wise<'+'>* rhs
。
编写*dot*
和*cross*
产品运算符也是显而易见的用途。
*
的使用可以扩展到支持其他分隔符,如+
。分隔符预测确定了命名运算符的预先确定性,这在将物理方程转换为 C++时可能很重要,而最少使用额外的 ()
s。
在上面的库中略有变化,我们可以支持 ->*then*
运算符并在标准更新之前扩展 std::function
,或者写一个单一的 ->*bind*
。它也可以有一个有状态的命名运算符,我们小心地将 Op
传递给最终的调用函数,允许:
named_operator<'*'> append = [](auto lhs, auto&& rhs) {
using std::begin; using std::end;
lhs.insert( end(lhs), begin(rhs), end(rhs) );
return std::move(lhs);
};
在 C++中生成一个命名的容器附加运算符 17。