命名运算符

你可以使用由标准 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。