namespace named_operator {
  template<class D>struct make_operator{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) );
  }
}
boilerplate library first, then:
namespace ops {
  struct concat_t:named_operator::make_operator<concat_t>{};
  static const concat_t concat{};
  template<class T, class A, class A2>
  std::vector<T,A> named_invoke( std::vector<T,A> lhs, concat_t, std::vector<T,A2> const& rhs){
    lhs.insert(lhs.end(), rhs.begin(), rhs.end());
    return std::move(lhs);
  }
}
using ops::concat;
writes the +concat+ operator.  End use looks like:
int main(){
   std::vector<int> a{1,2,3};
   std::vector<int> b{7,8,9};
  for( auto x: a +concat+ a +concat+ b )
    std::cout <<x<<'\n';
}
Live example.
Overloading naked + is illegal in std and fragile/dangerous in root namespace.  Fragile due to "in a subnamespace doea not work", dangerous if you make it too greedy.  concat_t tag type avoids both.
And who wants to call a function.  Prefix notation with ()s annoying to chain.
The above copies the left hand side (unless lhs is a temporary), then concatinates the rhs.  Adding move-contents to rhs is juat another named invoke function in namespace ops.  So a+a+b copies a, then extends the copy twice.
An expression template version could avoid hacing to resize more than once, for the usual issues.