命名運算子

你可以使用由標準 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。