C++
Перегрузка оператора
Поиск…
Вступление
В C ++ можно определить операторы, такие как +
и ->
для пользовательских типов. Например, заголовок <string>
определяет оператор +
для конкатенации строк. Это делается путем определения операторной функции с использованием ключевого слова operator
.
замечания
Операторы для встроенных типов не могут быть изменены, операторы могут быть перегружены только для пользовательских типов. То есть, по крайней мере, один из операндов должен быть определенного пользователем типа.
Следующие операторы не могут быть перегружены:
- Доступ к члену или «точка»
.
- Указатель на оператор доступа к члену
.*
- Оператор разрешения области,
::
- Тройной условный оператор,
?:
-
dynamic_cast
,static_cast
,reinterpret_cast
,const_cast
,typeid
,sizeof
,alignof
иnoexcept
- Директивы предварительной обработки,
#
и##
, которые выполняются перед любой информацией типа, доступны.
Есть некоторые операторы, которых вы не должны (99,98% времени) перегрузки:
-
&&
и||
(вместо этого следует использовать неявное преобразование вbool
) -
,
- Адрес-оператор (унарный
&
)
Зачем? Потому что они перегружают операторы, которых не может ожидать другой программист, что приводит к поведению, чем ожидалось.
Например, пользователь определил &&
и ||
перегрузки этих операторов теряют свою оценку короткого замыкания и теряют свое особые свойства секвенирования (C ++ 17) , вопрос секвенирования также относится и к ,
оператор перегрузка.
Арифметические операторы
Вы можете перегрузить все основные арифметические операторы:
-
+
и+=
-
-
и-=
-
*
и*=
-
/
и/=
-
&
and&=
-
|
и|=
-
^
и^=
-
>>
и>>=
-
<<
и<<=
Перегрузка для всех операторов одинакова. Прокрутите вниз для объяснения
Перегрузка вне class
/ struct
:
//operator+ should be implemented in terms of operator+=
T operator+(T lhs, const T& rhs)
{
lhs += rhs;
return lhs;
}
T& operator+=(T& lhs, const T& rhs)
{
//Perform addition
return lhs;
}
Перегрузка внутри class
/ struct
:
//operator+ should be implemented in terms of operator+=
T operator+(const T& rhs)
{
*this += rhs;
return *this;
}
T& operator+=(const T& rhs)
{
//Perform addition
return *this;
}
Примечание: operator+
должен возвращаться неконстантным значением, поскольку возврат ссылки не имеет смысла (он возвращает новый объект) и не возвращает значение const
(вы, как правило, не должны возвращать const
). Первый аргумент передается по значению, почему? Так как
- Вы не можете изменить исходный объект (
Object foobar = foo + bar;
не следует изменятьfoo
конце концов, это не имеет смысла) - Вы не можете сделать его
const
, потому что вам нужно будет иметь возможность изменять объект (потому чтоoperator+
реализован в терминахoperator+=
, который изменяет объект)
Передача const&
будет вариантом, но тогда вам придется сделать временную копию переданного объекта. Переходя по значению, компилятор делает это за вас.
operator+=
возвращает ссылку на себя, потому что тогда можно связать их (не используйте одну и ту же переменную, хотя это будет неопределенное поведение из-за точек последовательности).
Первый аргумент - это ссылка (мы хотим ее изменить), но не const
, потому что тогда вы не сможете ее изменить. Второй аргумент не должен быть изменен, поэтому аргумент производительности передается командой const&
(передача по заданию const быстрее, чем по значению).
Унарные операторы
Вы можете перегрузить два унарных оператора:
-
++foo
иfoo++
-
--foo
иfoo--
Перегрузка одинакова для обоих типов ( ++
и --
). Прокрутите вниз для объяснения
Перегрузка вне class
/ struct
:
//Prefix operator ++foo
T& operator++(T& lhs)
{
//Perform addition
return lhs;
}
//Postfix operator foo++ (int argument is used to separate pre- and postfix)
//Should be implemented in terms of ++foo (prefix operator)
T operator++(T& lhs, int)
{
T t(lhs);
++lhs;
return t;
}
Перегрузка внутри class
/ struct
:
//Prefix operator ++foo
T& operator++()
{
//Perform addition
return *this;
}
//Postfix operator foo++ (int argument is used to separate pre- and postfix)
//Should be implemented in terms of ++foo (prefix operator)
T operator++(int)
{
T t(*this);
++(*this);
return t;
}
Примечание. Оператор префикса возвращает ссылку на себя, чтобы продолжить работу над ним. Первый аргумент - это ссылка, поскольку оператор префикса меняет объект, это также причина, по которой он не const
(вы не смогли бы его изменить иначе).
Постфиксный оператор возвращает по значению временное (предыдущее значение), и поэтому он не может быть ссылкой, так как это будет ссылка на временную, которая будет значением мусора в конце функции, поскольку временная переменная гаснет объема). Он также не может быть const
, потому что вы должны иметь возможность изменять его напрямую.
Первый аргумент - это const
ссылка на объект «вызов», потому что если он был const
, вы не сможете его модифицировать, и если бы это не было ссылкой, вы не изменили бы исходное значение.
Это из-за копирования, необходимого для перегрузки операторов postfix, что лучше сделать привычкой использовать prefix ++ вместо postfix ++ in for
циклов. С for
точки контура, они , как правило , функционально эквивалентны, но может быть небольшое преимущество в производительности с использованием префикса ++, особенно с «тучные» классов с большим количеством участников для копирования. Пример использования префикса ++ в цикле for:
for (list<string>::const_iterator it = tokens.begin();
it != tokens.end();
++it) { // Don't use it++
...
}
Операторы сравнения
Вы можете перегрузить все операторы сравнения:
-
==
и!=
-
>
и<
-
>=
и<=
Рекомендуемый способ перегрузить все эти операторы - это реализовать только 2 оператора ( ==
и <
), а затем использовать их для определения остальных. Прокрутите вниз для объяснения
Перегрузка вне class
/ struct
:
//Only implement those 2
bool operator==(const T& lhs, const T& rhs) { /* Compare */ }
bool operator<(const T& lhs, const T& rhs) { /* Compare */ }
//Now you can define the rest
bool operator!=(const T& lhs, const T& rhs) { return !(lhs == rhs); }
bool operator>(const T& lhs, const T& rhs) { return rhs < lhs; }
bool operator<=(const T& lhs, const T& rhs) { return !(lhs > rhs); }
bool operator>=(const T& lhs, const T& rhs) { return !(lhs < rhs); }
Перегрузка внутри class
/ struct
:
//Note that the functions are const, because if they are not const, you wouldn't be able
//to call them if the object is const
//Only implement those 2
bool operator==(const T& rhs) const { /* Compare */ }
bool operator<(const T& rhs) const { /* Compare */ }
//Now you can define the rest
bool operator!=(const T& rhs) const { return !(*this == rhs); }
bool operator>(const T& rhs) const { return rhs < *this; }
bool operator<=(const T& rhs) const { return !(*this > rhs); }
bool operator>=(const T& rhs) const { return !(*this < rhs); }
Операторы, очевидно, возвращают bool
, указав true
или false
для соответствующей операции.
Все операторы принимают свои аргументы const&
потому, что единственное, что делают операторы, - это сравнивать, поэтому они не должны изменять объекты. Проходя мимо &
(ссылка) быстрее , чем по значению, и , чтобы убедиться , что операторы не изменяют его, это const
-reference.
Обратите внимание, что операторы внутри class
/ struct
определены как const
, причина в том, что без функций const
, сравнение объектов const
будет невозможно, поскольку компилятор не знает, что операторы ничего не изменяют.
Операторы преобразования
Вы можете перегружать операторы типа, чтобы ваш тип мог быть неявно преобразован в указанный тип.
Оператор преобразования должен быть определен в class
/ struct
:
operator T() const { /* return something */ }
Примечание: оператор const
является const
позволяющей преобразовывать объекты const
.
Пример:
struct Text
{
std::string text;
// Now Text can be implicitly converted into a const char*
/*explicit*/ operator const char*() const { return text.data(); }
// ^^^^^^^
// to disable implicit conversion
};
Text t;
t.text = "Hello world!";
//Ok
const char* copyoftext = t;
Оператор индексирования массива
Вы даже можете перегрузить оператор индексирования массива []
.
Вы всегда должны (99.98% времени) реализовывать 2 версии, версию const
и not- const
, потому что, если объект const
, он не должен изменять объект, возвращенный []
.
Аргументы передаются const&
вместо значения, потому что передача по ссылке быстрее, чем по значению, и const
чтобы оператор не менял индекс случайно.
Операторы возвращаются по ссылке, потому что по дизайну вы можете изменить объект []
return, то есть:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
Вы можете перегружать только внутри class
/ struct
:
//I is the index type, normally an int
T& operator[](const I& index)
{
//Do something
//return something
}
//I is the index type, normally an int
const T& operator[](const I& index) const
{
//Do something
//return something
}
Множественные индексы, [][]...
, могут быть достигнуты через прокси-объекты. Следующий пример простого строкового матричного класса демонстрирует следующее:
template<class T>
class matrix {
// class enabling [][] overload to access matrix elements
template <class C>
class proxy_row_vector {
using reference = decltype(std::declval<C>()[0]);
using const_reference = decltype(std::declval<C const>()[0]);
public:
proxy_row_vector(C& _vec, std::size_t _r_ind, std::size_t _cols)
: vec(_vec), row_index(_r_ind), cols(_cols) {}
const_reference operator[](std::size_t _col_index) const {
return vec[row_index*cols + _col_index];
}
reference operator[](std::size_t _col_index) {
return vec[row_index*cols + _col_index];
}
private:
C& vec;
std::size_t row_index; // row index to access
std::size_t cols; // number of columns in matrix
};
using const_proxy = proxy_row_vector<const std::vector<T>>;
using proxy = proxy_row_vector<std::vector<T>>;
public:
matrix() : mtx(), rows(0), cols(0) {}
matrix(std::size_t _rows, std::size_t _cols)
: mtx(_rows*_cols), rows(_rows), cols(_cols) {}
// call operator[] followed by another [] call to access matrix elements
const_proxy operator[](std::size_t _row_index) const {
return const_proxy(mtx, _row_index, cols);
}
proxy operator[](std::size_t _row_index) {
return proxy(mtx, _row_index, cols);
}
private:
std::vector<T> mtx;
std::size_t rows;
std::size_t cols;
};
Оператор вызова функции
Вы можете перегрузить оператор вызова функции ()
:
Перегрузка должна выполняться внутри class
/ struct
:
//R -> Return type
//Types -> any different type
R operator()(Type name, Type2 name2, ...)
{
//Do something
//return something
}
//Use it like this (R is return type, a and b are variables)
R foo = object(a, b, ...);
Например:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2
Оператор присваивания
Оператор присваивания является одним из наиболее важных операторов, поскольку он позволяет изменять статус переменной.
Если вы не перегружаете оператор-ассистент для своего class
/ struct
, он автоматически генерируется компилятором: автоматически созданный оператор присваивания выполняет «членное присвоение», то есть путем вызова операторов присваивания всем членам, так что один объект копируется другому, член в момент времени. Оператор присваивания должен быть перегружен, когда простое назначение по порядку не подходит для вашего class
/ struct
, например, если вам нужно выполнить глубокую копию объекта.
Перегрузка оператора присваивания =
легко, но вы должны выполнить несколько простых шагов.
- Тест для самостоятельного назначения. Эта проверка важна по двум причинам:
- самоназначение - это ненужная копия, поэтому нет смысла ее выполнять;
- следующий шаг не будет работать в случае самоопределения.
- Очистите старые данные. Старые данные должны быть заменены новыми. Теперь вы можете понять вторую причину предыдущего шага: если содержимое объекта было уничтожено, самоопределение не сможет выполнить копию.
- Скопируйте всех участников. Если вы перегружаете оператор-ассистент для своего
class
или вашейstruct
, он не генерируется автоматически компилятором, поэтому вам нужно будет взять на себя ответственность за копирование всех членов из другого объекта. - Верните
*this
. Оператор возвращает себя по ссылке, потому что он позволяет связывать (т.int b = (a = 6) + 4; //b == 10
).
//T is some type
T& operator=(const T& other)
{
//Do something (like copying values)
return *this;
}
Примечание: other
передается const&
потому, что назначаемый объект не должен быть изменен, а передача по ссылке быстрее, чем по значению, и чтобы убедиться, что operator=
не модифицирует его случайно, это const
.
Оператор присваивания может только быть перегружен в class
/ struct
, так как слева значение =
всегда class
/ struct
сама по себе. Из-за этого определение этой функции как свободной функции не имеет этой гарантии.
Когда вы объявляете его в class
/ struct
, левое значение неявно является самим class
/ struct
, поэтому с этим не возникает никаких проблем.
Побитовый оператор NOT
Перегрузка побитового NOT ( ~
) довольно проста. Прокрутите вниз для объяснения
Перегрузка вне class
/ struct
:
T operator~(T lhs)
{
//Do operation
return lhs;
}
Перегрузка внутри class
/ struct
:
T operator~()
{
T t(*this);
//Do operation
return t;
}
Примечание: operator~
возвращает значение, потому что он должен возвращать новое значение (измененное значение), а не ссылку на значение (это будет ссылка на временный объект, который будет иметь значение мусора в нем, как только оператор выполнен). Не const
потому что вызывающий код должен иметь возможность модифицировать его впоследствии (т. int a = ~a + 1;
должно быть возможно).
Внутри class
/ struct
вы должны сделать временный объект, потому что вы не можете изменить this
, как бы изменить исходный объект, который не должен быть так.
Операторы сдвига битов для ввода / вывода
Операторы <<
и >>
обычно используются как операторы «write» и «read»:
-
std::ostream
перегружает<<
для записи переменных в базовый поток (пример:std::cout
) -
std::istream
overloads>>
читать из базового потока в переменную (пример:std::cin
)
То, как они это делают, аналогично, если вы хотели перегрузить их «нормально» вне class
/ struct
, за исключением того, что указание аргументов не одного типа:
- Тип возврата - это поток, который вы хотите перегрузить (например,
std::ostream
), переданный по ссылке, чтобы обеспечить цепочку (Chaining:std::cout << a << b;
). Пример:std::ostream&
-
lhs
будет таким же, как тип возврата -
rhs
типа вы хотите разрешить перегрузку из (т.е.T
), принятыйconst&
вместо значения по причине производительности (rhs
не должны быть изменены в любом случае). Пример:const Vector&
.
Пример:
//Overload std::ostream operator<< to allow output from Vector's
std::ostream& operator<<(std::ostream& lhs, const Vector& rhs)
{
lhs << "x: " << rhs.x << " y: " << rhs.y << " z: " << rhs.z << '\n';
return lhs;
}
Vector v = { 1, 2, 3};
//Now you can do
std::cout << v;
Сложные номера
В приведенном ниже коде реализована очень простая сложная тип номера, для которой автоматически создается базовое поле, следуя правилам продвижения типа языка, при применении четырех основных операторов (+, -, * и /) с членом другого поля (будь то другой complex<T>
или некоторый скалярный тип).
Это предназначено для целостного примера, охватывающего перегрузку оператора наряду с основным использованием шаблонов.
#include <type_traits>
namespace not_std{
using std::decay_t;
//----------------------------------------------------------------
// complex< value_t >
//----------------------------------------------------------------
template<typename value_t>
struct complex
{
value_t x;
value_t y;
complex &operator += (const value_t &x)
{
this->x += x;
return *this;
}
complex &operator += (const complex &other)
{
this->x += other.x;
this->y += other.y;
return *this;
}
complex &operator -= (const value_t &x)
{
this->x -= x;
return *this;
}
complex &operator -= (const complex &other)
{
this->x -= other.x;
this->y -= other.y;
return *this;
}
complex &operator *= (const value_t &s)
{
this->x *= s;
this->y *= s;
return *this;
}
complex &operator *= (const complex &other)
{
(*this) = (*this) * other;
return *this;
}
complex &operator /= (const value_t &s)
{
this->x /= s;
this->y /= s;
return *this;
}
complex &operator /= (const complex &other)
{
(*this) = (*this) / other;
return *this;
}
complex(const value_t &x, const value_t &y)
: x{x}
, y{y}
{}
template<typename other_value_t>
explicit complex(const complex<other_value_t> &other)
: x{static_cast<const value_t &>(other.x)}
, y{static_cast<const value_t &>(other.y)}
{}
complex &operator = (const complex &) = default;
complex &operator = (complex &&) = default;
complex(const complex &) = default;
complex(complex &&) = default;
complex() = default;
};
// Absolute value squared
template<typename value_t>
value_t absqr(const complex<value_t> &z)
{ return z.x*z.x + z.y*z.y; }
//----------------------------------------------------------------
// operator - (negation)
//----------------------------------------------------------------
template<typename value_t>
complex<value_t> operator - (const complex<value_t> &z)
{ return {-z.x, -z.y}; }
//----------------------------------------------------------------
// operator +
//----------------------------------------------------------------
template<typename left_t,typename right_t>
auto operator + (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x + b.x)>>
{ return{a.x + b.x, a.y + b.y}; }
template<typename left_t,typename right_t>
auto operator + (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a + b.x)>>
{ return{a + b.x, b.y}; }
template<typename left_t,typename right_t>
auto operator + (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x + b)>>
{ return{a.x + b, a.y}; }
//----------------------------------------------------------------
// operator -
//----------------------------------------------------------------
template<typename left_t,typename right_t>
auto operator - (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x - b.x)>>
{ return{a.x - b.x, a.y - b.y}; }
template<typename left_t,typename right_t>
auto operator - (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a - b.x)>>
{ return{a - b.x, - b.y}; }
template<typename left_t,typename right_t>
auto operator - (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x - b)>>
{ return{a.x - b, a.y}; }
//----------------------------------------------------------------
// operator *
//----------------------------------------------------------------
template<typename left_t, typename right_t>
auto operator * (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x * b.x)>>
{
return {
a.x*b.x - a.y*b.y,
a.x*b.y + a.y*b.x
};
}
template<typename left_t, typename right_t>
auto operator * (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a * b.x)>>
{ return {a * b.x, a * b.y}; }
template<typename left_t, typename right_t>
auto operator * (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x * b)>>
{ return {a.x * b, a.y * b}; }
//----------------------------------------------------------------
// operator /
//----------------------------------------------------------------
template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x / b.x)>>
{
const auto r = absqr(b);
return {
( a.x*b.x + a.y*b.y) / r,
(-a.x*b.y + a.y*b.x) / r
};
}
template<typename left_t, typename right_t>
auto operator / (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a / b.x)>>
{
const auto s = a/absqr(b);
return {
b.x * s,
-b.y * s
};
}
template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x / b)>>
{ return {a.x / b, a.y / b}; }
}// namespace not_std
int main(int argc, char **argv)
{
using namespace not_std;
complex<float> fz{4.0f, 1.0f};
// makes a complex<double>
auto dz = fz * 1.0;
// still a complex<double>
auto idz = 1.0f/dz;
// also a complex<double>
auto one = dz * idz;
// a complex<double> again
auto one_again = fz * idz;
// Operator tests, just to make sure everything compiles.
complex<float> a{1.0f, -2.0f};
complex<double> b{3.0, -4.0};
// All of these are complex<double>
auto c0 = a + b;
auto c1 = a - b;
auto c2 = a * b;
auto c3 = a / b;
// All of these are complex<float>
auto d0 = a + 1;
auto d1 = 1 + a;
auto d2 = a - 1;
auto d3 = 1 - a;
auto d4 = a * 1;
auto d5 = 1 * a;
auto d6 = a / 1;
auto d7 = 1 / a;
// All of these are complex<double>
auto e0 = b + 1;
auto e1 = 1 + b;
auto e2 = b - 1;
auto e3 = 1 - b;
auto e4 = b * 1;
auto e5 = 1 * b;
auto e6 = b / 1;
auto e7 = 1 / b;
return 0;
}
Именованные операторы
Вы можете расширить 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
типа append_t:named_operator::make_operator<append_t>
.
Затем мы перегружаем named_invoke (lhs, append_t, rhs) для типов, которые мы хотим, справа и слева.
Библиотека перегружает lhs*append_t
, возвращая временный объект half_apply
. Он также перегружает half_apply*rhs
для вызова named_invoke( lhs, append_t, rhs )
.
Нам просто нужно создать правильный токен append_t
и сделать ADL-friendly 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
до обновляемого стандарта или писать monadic ->*bind*
. У него также может быть оператор с именем stateful, где мы тщательно передаем Op
до последней функции invoke, разрешая:
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.