Другие операторы
Имя оператора | Синтаксис | Перегружаемый | Примеры прототипов (для class T) | |
---|---|---|---|---|
Определение внутри класса | Определение вне класса | |||
вызов функции | a(a1, a2) | Да | R T::operator()(Арг1&a1, Арг2&a2, ... ...); | Н/Д |
запятая | a, b | Да | T2& T::operator,(T2 &b); | T2& operator,(const T &a, T2 &b); |
условный оператор | a ? b : c | Нет | Н/Д | Н/Д |
Содержание |
[править]Объяснение
Оператор вызов функции обеспечивает семантику функции для любого объекта.
Условный оператор (математически формально именуемый тернарной условной операцией) проверяет логическое значение первого выражения и, в зависимости от полученного значения, оценивает и возвращает либо второе, либо третье выражение.
[править]Встроенный оператор вызова функции
Выражения вызова функции имеют вид
функция ( арг1, арг2, арг3, ...) | |||||||||
где
- функция это тип выражения функции или тип указателя на функцию, а
- арг1
,
арг2,
арг3,
... возможно, пустой список произвольных выражений или списки инициализации в фигурных скобках(начиная с C++11), за исключением того, что оператор запятая не разрешён на верхнем уровне, чтобы избежать двусмысленности.
Для вызова функции, не являющейся элементом, или статической функции-элемента, функция может быть lvalue, которое ссылается на функцию (в этом случае нет названия раздела подавляется), или значение prvalue типа указателя на функцию.
Имя функции (или элемента), указанное в функция может быть перегружено, используются правила разрешения перегрузки для определения того, какая перегрузка должна быть вызвана.
Если функция указывает функцию-элемент, она может быть виртуальной, и в этом случае будет вызываться окончательная перегрузка этой функции с использованием динамической диспетчеризации во время выполнения.
Чтобы вызвать функцию,
Выражение функция, а также все выражения arg1, arg2, arg3 и.д., предоставленные в качестве аргументов, оцениваются в произвольном порядке, непоследовательно по отношению друг к другу. | (до C++17) |
Выражение функция упорядочивается перед каждым из выражений arg1, arg2, arg3 а также перед {rlp | (начиная с C++17) |
}} Каждый параметр функции инициализируется соответствующим аргументом после неявного преобразования, если это необходимо. Если соответствующего аргумента нет, используется соответствующий аргумент по умолчанию, а если его нет, программа не корректна. Если выполняется вызов функции-элемента, тогда указатель this на текущий объект преобразуется, как явное приведение к указателю this, ожидаемому функцией. Инициализация и уничтожение каждого параметра происходит в контексте вызывающей стороны, что означает, например, что если конструктор параметра выдаёт исключение, обработчики исключений, определённые в функции, даже как блок try функции, не рассматриваются. Если функция является вариативной функцией, продвижение аргументов по умолчанию применяются ко всем аргументам, соответствующим параметру многоточие. Заканчивается ли время жизни параметра, когда функция, в которой он определён, завершается, или в конце включающего полного выражения, определяется реализацией.
Тип возвращаемого значения выражения вызова функции это тип возвращаемого значения выбранной функции, определённый с использованием статической привязки (игнорируя ключевое слово virtual), даже если фактически вызванная перегруженная функция возвращает другой тип. Это позволяет перегруженным функциям возвращать указатели или ссылки на классы, которые являются производными от типа возвращаемого значения, возвращаемого базовой функцией, т.е. C++ поддерживает ковариантные возвращаемые типы. Если функция указывает деструктор, возвращаемый тип void.
Когда объект типа класса Временный объект создаётся из аргумента функции или возвращаемого значения, соответственно, и параметр функции или возвращаемый объект инициализируется, как если бы с использованием неудалённого тривиального конструктора для копирования временного объекта (даже если этот конструктор недоступен или не будет выбран по разрешению перегрузки для выполнения копирования или перемещения объекта). Это позволяет передавать или возвращать объекты небольших типов классов, такие как std::complex или std::span из функций в регистрах. | (начиная с C++17) |
Категория значения выражения вызова функции это lvalue, если функция возвращает ссылку lvalue или ссылку rvalue на функцию, или xvalue, если функция возвращает ссылку rvalue на объект, и в противном случае это prvalue. Если выражение вызова функции является prvalue объектного типа, оно должно иметь полный тип, кроме , когда prvalue не материализовано, например(начиная с C++17) при использовании в качестве операнда decltype
(или как правый операнд встроенного выражения оператора запятая, который является операндом decltype).
Выражение вызова функции похоже по синтаксису на инициализацию значения T(), на выражение приведения в стиле функцииT(A1) и на прямую инициализацию временного объекта T(A1, A2, A3, ...), где T
имя типа.
#include <cstdio>struct S {int f1(double d){return printf("%f \n", d);// вызов функции с переменным аргументом}int f2(){return f1(7);// вызов функции-элемента, тоже самое, что и this->f1()// целочисленный аргумент преобразован в double}};void f(){ puts("функция вызвана");// вызов функции}int main(){ f();// вызов функции S s; s.f2();// вызов функции-элемента}
Вывод:
функция вызвана 7.000000
[править]Встроенный оператор запятая
Выражения оператора запятая имеют вид
E1, E2 | |||||||||
В выражении с запятой E1, E2 вычисляется выражение E1
, его результат отбрасывается (хотя, если оно имеет тип класса, оно не будет уничтожено до конца содержащего его полного выражения), и его побочные эффекты завершатся до начала оценки выражения E2
(обратите внимание, что определяемый пользователем operator,
не может гарантировать последовательность)(до C++17).
Тип, значение и категория значения результата выражения запятая являются в точности типом, значением и категорией значения второго операнда E2
. Если E2
является временным выражением(начиная с C++17), результат выражения будет временным выражением(начиная с C++17). Если E2
является битовым полем, результатом будет битовое поле.
Запятая в различных списках, разделённых запятыми, таких как списки аргументов функций (f(a, b, c)) и списки инициализаторов int a[]={1,2,3}, не является оператором запятой. Если в таких контекстах необходимо использовать оператор запятая, его следует заключить в круглые скобки: f(a, (n++, n+b), c)
Использование выражения запятая без скобок в качестве второго (правого) аргумента оператора индексации не рекомендуется. Например, a[b, c] не рекомендуется, а a[(b, c)] можно использовать. | (начиная с C++20) (до C++23) |
Выражение с запятой без скобок не может быть вторым (правым) аргументом оператора индексации. Например, a[b, c] неверно сформировано или эквивалентно a.operator[](b, c). Для использования выражения запятая в качестве индекса необходимы скобки, например, a[(b, c)]. | (начиная с C++23) |
#include <iostream>int main(){// запятая часто используется для выполнения более чем одного// выражения, если грамматика языка допускает только одно выражение: // * в третьем компоненте цикла forfor(int i =0, j =10; i <= j;++i, --j)// ^разделитель списка ^оператор запятаяstd::cout<<"i = "<< i <<" j = "<< j <<'\n'; // * в операторе return// return log("ошибка!"), -1; // * в выражении инициализатора// MyClass( const Arg& arg)// : member{ throws_if_bad(arg), arg } // и т.д. // операторы-запятая могут быть объединены в цепочку; результат последнего// (самого правого) выражения является результатом всей цепочки:int n =1;int m =(++n, std::cout<<"n = "<< n <<'\n', ++n, 2*n);// m сейчас 6std::cout<<"m = "<<(++m, m)<<'\n';}
Вывод:
i = 0 j = 10 i = 1 j = 9 i = 2 j = 8 i = 3 j = 7 i = 4 j = 6 i = 5 j = 5 n = 2 m = 7
[править]Условный оператор
Выражения условных операторов имеют вид
E1? E2: E3 | |||||||||
Первый операнд условного оператора оценивается и контекстно преобразуется в bool. После завершения оценки значения и всех побочных эффектов первого операнда, если результат был true, оценивается второй операнд. Если результат был false, оценивается третий операнд.
Тип и категория значения условного выражения E1 ? E2 : E3 определяются в соответствии со следующими правилами:
E2
или E3
имеет тип void, то должно выполняться одно из следующих условий, или программа не корректна:E2
, либо E3
(но не оба сразу) является (возможно, заключенным в скобки) выражением throw. Результат условного оператора имеет тип и категорию значения другого выражения. Если другое выражение это битовое поле, результатом будет битовое поле. Такой условный оператор обычно использовался в C++11 constexpr программировании до C++14. std::string str =2+2==4?"ok":throwstd::logic_error("2+2 != 4");
E2
, и E3
являются типом void (включая случай, когда они оба являются выражениями throw). Результатом является prvalue типа void. 2+2==4?throw123:throw456;
2) Иначе, если E2 или E3 являются битовыми полями glvalue той же категории значений и типов cv1 T и cv2 T, соответственно, операнды считаются имеющими тип cv T для остальной части этого раздела, где cv это объединение cv1 и cv2. | (начиная с C++14) |
E2
и E3
имеют разные типы, по крайней мере один из которых является (возможно, cv-квалифицированным) типом класса, или оба являются значениями glvalue одной и той же категории значений и имеют один и тот же тип, за исключением cv-квалификации, тогда делается попытка сформировать последовательность неявного преобразования игнорируя доступ к элементу, является ли операнд битовым полем, или функция преобразования удалена от каждого из операндов до целевого типа, определённого другим операндом(начиная с C++11), как описано ниже. Операнд (назовём его X
) типа TX
может быть преобразован в целевой тип другого операнда (назовём его Y
) типа TY
следующим образом:Y
является lvalue, целевой тип это TY&
, и ссылка должна напрямую связываться с lvalue;Y
является xvalue, целевой тип это TY&&
, и ссылка должна быть привязана напрямую;Y
является prvalue, или если ни одна из вышеуказанных последовательностей преобразования не может быть сформирована и по крайней мере одно из TX
и TY
является (возможно, cv-квалифицированным) типом класса,TX
и TY
относятся к одному и тому же типу класса (игнорируя cv-квалификацию) и TY
cv-квалифицирован, как TX
, целевой тип это TY
,TY
является базовым классом TX
, целевой тип это TY
с cv-квалификаторами TX
struct A {};struct B : A {};using T =const B; A a =true? A(): T();// Y = A(), TY = A, X = T(), TX = const B. Целевой тип = const A
Y
будет после применения стандартных преобразований lvalue-в-rvalue, массива в указатель и функции в указательE2
для целевого типа E3
и E3
для целевого типа E2
), или может быть сформирована только одна, то это неоднозначная последовательность преобразования, программа некорректна.E2
и E3
являются glvalue одного типа и одной и той же категории значений, то результат имеет тот же тип и категорию значений и является битовым полем, если хотя бы одно из E2
и E3
битовое поле.E2
и E3
не одного и того же типа, и любой из них имеет (возможно, cv-квалифицированный) тип класса, разрешение перегрузки выполняется с использованием встроенных кандидатов, указанных ниже, чтобы попытаться преобразовать операнды во встроенные типы. Если разрешение перегрузки не удаётся, программа некорректна. В противном случае применяются выбранные преобразования, а преобразованные операнды используются вместо исходных операндов для шага 6.E2
и E3
теперь имеют один и тот же тип, результатом будет prvalue этого типа , обозначающее временный объект инициализированный(до C++17)объект результата которого инициализирован(начиная с C++17) копией из любого операнда, выбранного после оценки E1
.E2
, и E3
имеют арифметический или перечисляемый тип: применяются обычные арифметические преобразования для приведения их к общему типу, и этот тип является результатом.E2
, и E3
являются указателями, или один является указателем, а другой константой нулевого указателя, то применяются преобразования указателей и квалификационные преобразования, чтобы привести их к общему типу, и этот тип и есть результат. int*intPtr;// nullptr становится int* static_assert(std::is_same_v<decltype(true?nullptr:intPtr), int*>);
E2
, и E3
являются указателями на элементы, или один указатель на элемент, а другой константа нулевого указателя, то применяются преобразования указателя на элемент и квалификационные преобразования, чтобы привести их к общему типу, и этот тип является результатом. struct A{int* m_ptr;} a;int* A::* memPtr =&A::m_ptr;// memPtr это указатель на элемент m_ptr в A static_assert(std::is_same_v<decltype(false?memPtr:nullptr), int*A::*>);// memPtr делает nullptr типом указателя на элемент m_ptr из A static_assert(std::is_same_v<decltype(false?a.*memPtr:nullptr), int*>);// a.*memPtr теперь просто указатель на int, и nullptr также становится// указателем на int
E2
, и E3
являются константами нулевого указателя и хотя бы одна из них имеет тип std::nullptr_t, то тип результата будет std::nullptr_t
.Этот раздел не завершён Причина: есть ли шанс сделать это более читаемым без потери тонкостей? По крайней мере, однострочный микро-пример для каждого пункта списка очень бы помог |
Для каждой пары продвинутых арифметических типов L
и R
и для каждого типа P
, где P
это указатель, указатель на элемент или тип перечисления с ограниченной областью видимости, следующие сигнатуры функций участвуют в разрешении перегрузки, выполняемом на шаге 5 приведенных выше правил:
LR operator?:(bool, L, R ); | ||
P operator?:(bool, P, P ); | ||
где LR это результат обычных арифметических преобразований, выполненных над L
и R
. Оператор “?:
” не может быть перегружен, эти сигнатуры функций существуют только для разрешения перегрузки.
Возвращаемый тип условного оператора также доступен как свойство двоичного типа std::common_type.
#include <iostream>#include <string>struct Node { Node* next;int data;// конструктор копирования с глубоким копированием Node(const Node& other): next(other.next? new Node(*other.next):NULL) , data(other.data){} Node(int d): next(NULL), data(d){} ~Node(){ delete next ;}};int main(){// простой пример rvalueint n =1>2?10:11;// 1>2 ложь, поэтому n = 11// простой пример lvalueint m =10;(n == m ? n : m)=7;// n == m ложь, поэтому m = 7std::cout<<"n = "<< n <<"\nm = "<< m;//вывод результата}
Вывод:
n = 11 m = 7
[править]Стандартная библиотека
Многие классы стандартной библиотеки перегружают operator()
для использования в качестве объектов функций.
удаляет объект или массив (public функция-элемент std::default_delete ) | |
возвращает сумму двух аргументов (public функция-элемент std::plus ) | |
возвращает разницу между двумя аргументами (public функция-элемент std::minus ) | |
возвращает произведение двух аргументов (public функция-элемент std::multiplies ) | |
возвращает результат деления первого аргумента на второй аргумент (public функция-элемент std::divides ) | |
возвращает остаток от деления первого аргумента на второй аргумент (public функция-элемент std::modulus ) | |
возвращает отрицание аргумента (public функция-элемент std::negate ) | |
проверяет, равны ли аргументы (public функция-элемент std::equal_to ) | |
проверяет аргументы на неравенство (public функция-элемент std::not_equal_to ) | |
проверяет, больше ли первый аргумент второго (public функция-элемент std::greater ) | |
проверяет, меньше ли первый аргумент второго (public функция-элемент std::less ) | |
проверяет, больше ли первый аргумент второго или равен ему (public функция-элемент std::greater_equal ) | |
проверяет, меньше ли первый аргумент второго или равен ему (public функция-элемент std::less_equal ) | |
возвращает логическое И двух аргументов (public функция-элемент std::logical_and ) | |
возвращает логическое ИЛИ двух аргументов (public функция-элемент std::logical_or ) | |
возвращает логическое НЕ аргумента (public функция-элемент std::logical_not ) | |
возвращает результат поразрядного И двух аргументов (public функция-элемент std::bit_and ) | |
возвращает результат поразрядного ИЛИ двух аргументов (public функция-элемент std::bit_or ) | |
возвращает результат побитового XOR двух аргументов (public функция-элемент std::bit_xor ) | |
возвращает логическое дополнение результата вызова сохранённого предиката (public функция-элемент std::unary_negate ) | |
возвращает логическое дополнение результата вызова сохранённого предиката (public функция-элемент std::binary_negate ) | |
вызывает сохранённую функцию (public функция-элемент std::reference_wrapper ) | |
вызывает цель (public функция-элемент std::function<R(Args...)> ) | |
лексикографически сравнивает две строки, используя фасет сопоставления этой локали (public функция-элемент std::locale ) | |
сравнивает два значения типа value_type (public функция-элемент std::map::value_compare ) | |
сравнивает два значения типа value_type (public функция-элемент std::multimap::value_compare ) | |
выполняет функцию (public функция-элемент std::packaged_task ) | |
(C++11) | продвигает состояние движка и возвращает сгенерированное значение (public функция-элемент std::linear_congruential_engine ) |
(C++11) | генерирует следующее случайное число в распределении (public функция-элемент std::uniform_int_distribution ) |
Оператор запятая не перегружен ни одним классом стандартной библиотеки. Библиотека boost использует operator, в boost.assign, boost.spirit и других библиотеках. Библиотека доступа к базе данных SOCI также перегружает operator,.
[править]Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 446 | C++98 | не было указано, создаётся ли временный объект для преобразования lvalue-в-rvalue в условном операторе | всегда создаёт временный объект, если оператор возвращает класс rvalue |
CWG 462 | C++98 | если второй операнд выражения с запятой является временным, не указано, будет ли продлено его время жизни, когда результат выражения с запятой будет привязан к ссылке | результат выражения запятой в этом случае является временным (следовательно, его время жизни продлевается) |
CWG 587 | C++98 | когда второй и третий операнды условного выражения являются значениями lvalue одного и того же типа, за исключением cv-квалификации, результатом было значение lvalue, если эти операнды имеют классовые типы, или значение rvalue в противном случае | в этом случае результатом всегда является lvalue |
CWG 1029 | C++98 | тип вызова деструктора не был специфицирован | специфицирован как void |
CWG 1550 | C++98 | выражение throw в скобках не разрешалось в условных выражениях, если другой операнд не являлся void | разрешено |
CWG 1560 | C++98 | void операнд в условных выражениях вызывал необоснованное преобразование lvalue-в-rvalue для другого операнда, что всегда приводило к rvalue | условное выражение с void может быть lvalue |
CWG 1642 | C++98 | выражение функция в выражении вызова функции может быть lvalue указателем на функцию | не допускается |
CWG 1805 | C++98 | в случае 3.3.3) условных выражений преобразования массива в указатель и функции в указатель не применялись | применяются преобразования |
CWG 1895 | C++14 | неясно, предотвращает ли удалённая или недоступная функция преобразования преобразование в условных выражениях, а преобразования из базового класса в производный класс prvalue не учитывались | обрабатывается как разрешение перегрузки |
CWG 1932 | C++14 | битовые поля одного типа отсутствовали в условных выражениях | обрабатывается базовыми типами |
CWG 2226 | C++11 | при определении целевого типа другого операнда условного оператора ссылка не может быть привязана к xvalue, если этот операнд является lvalue | позволено |
CWG 2321 | C++98 | при определении целевого типа другого операнда условного оператора тип производного класса не мог быть преобразован в тип базового класса с меньшей cv-квалификацией | разрешено преобразовывать в тип базового класса с cv-квалификацией из операнда производного класса |
[править]Смотрите также
Приоритет операторовПерегрузка операторов
Общие операторы | ||||||
---|---|---|---|---|---|---|
присваивание | инкремент декремент | арифметические | логические | сравнения | доступ к элементу | другие |
a = b | ++a | +a | !a | a == b | a[...] | вызов функции |
a(...) | ||||||
запятая | ||||||
a, b | ||||||
условный | ||||||
a ? b : c | ||||||
Специальные операторы | ||||||
static_cast приводит один тип к другому совместимому типу |
Документация C по Другие операторы |