Spec-Zone.ru › C++

Другие операторы

Название
оператора
Синтаксис Перегружаемый Примеры прототипов (для classT)
Внутри определения класса Вне определения класса
вызов функции a(a1, a2) Да R T::operator()(Arg1 &a1, Arg2 &a2, ...); Н/Д
запятая a, b Да T2& T::operator,(T2 &b); T2& operator,(const T &a, T2 &b);
условный оператор a ? b : c Нет Н/Д Н/Д

Оператор вызова функции предоставляет семантику функции для любого объекта.

Условный оператор (в просторечии называемый тернарным условным оператором ) проверяет логическое значение первого выражения и, в зависимости от полученного значения, вычисляет и возвращает второе или третье выражение.

Встроенный оператор вызова функции

Выражения вызова функции имеют следующую форму:

function (arg1, arg2, arg3,...)
function - выражение типа функции или типа указателя на функцию
arg1, arg2, arg3,... - возможно, пустой список произвольных выражений или списков инициализации в фигурных скобках, ограниченных фигурными скобками(начиная с C++11), за исключением того, что оператор запятая не допускается на верхнем уровне во избежание неоднозначности

При вызове нестатической функции или статической функции-члена function может быть lvalue, ссылающимся на функцию (в этом случае преобразование функции в указатель подавляется), либо prvalue типа указателя на функцию.

Имя функции (или члена), заданное посредством function, может быть перегружено; правила разрешения перегрузки используются для определения вызываемой перегрузки.

Если function задаёт функцию-член, она может быть виртуальной; в таком случае вызывается конечная переопределяющая функция с использованием динамической диспетчеризации во время выполнения.

Каждый параметр функции инициализируется соответствующим аргументом после выполнения неявного преобразования, если оно необходимо.

  • Если соответствующего аргумента нет, используется соответствующий аргумент по умолчанию; если его также нет, программа некорректна.
  • Если вызов производится для функции-члена, то указатель this на текущий объект преобразуется, как если бы применялось явное приведение к указателю this, ожидаемому функцией.
  • Инициализация и уничтожение каждого параметра происходят в контексте полного выражения, в котором появляется вызов функции. Это означает, например, что если конструктор или деструктор параметра выбрасывает исключение, то блоки try функции, в которой он был вызван, не рассматриваются.

Если функция является функцией с переменным числом аргументов, ко всем аргументам, соответствующим параметру-многоточию, применяются стандартные повышения аргументов.

Поведение реализации определяет, уничтожается ли параметр при выходе из функции, в которой он определён, или в конце охватывающего полного выражения. Параметры всегда уничтожаются в порядке, обратном порядку их создания.

Тип выражения вызова функции — это тип возвращаемого значения выбранной функции, определяемый статическим связыванием (без учёта ключевого слова virtual), даже если фактически вызванная переопределяющая функция возвращает другой тип. Это позволяет переопределяющим функциям возвращать указатели или ссылки на классы, производные от класса типа, возвращаемого базовой функцией, то есть C++ поддерживает ковариантные типы возвращаемого значения. Если function задаёт деструктор, типом возвращаемого значения является void.

Когда объект типа класса X передаётся в функцию или возвращается из неё, если каждый конструктор копирования, конструктор перемещения и деструктор X является тривиальным или удалённым, а X имеет хотя бы один не удалённый конструктор копирования или перемещения, реализации разрешается создать временный объект для хранения параметра функции или объекта результата.

Временный объект конструируется соответственно из аргумента функции или возвращаемого значения, а параметр функции или объект результата инициализируется так, как если бы для копирования временного объекта использовался не удалённый тривиальный конструктор (даже если этот конструктор недоступен или не был бы выбран разрешением перегрузки для копирования или перемещения объекта).

Это позволяет передавать в функции и возвращать из них в регистрах объекты небольших типов классов, например std::complex или std::span.

(начиная с C++17)

Категория значения выражения вызова функции — lvalue, если функция возвращает lvalue-ссылку или rvalue-ссылку на функцию; xvalue, если функция возвращает rvalue-ссылку на объект; и prvalue в остальных случаях. Если выражение вызова функции является prvalue объектного типа, оно должно иметь полный тип, за исключением случаев, когда оно используется в качестве операнда decltype (или в качестве правого операнда встроенного оператора запятая, который является операндом decltype)(начиная с C++11).

При нормальном выходе из вызванной функции все постусловные утверждения функции вычисляются последовательно. Если реализация вводит какие-либо временные объекты для хранения значения результата, для вычисления E каждого постусловного утверждения:

  • E выполняется неопределённо упорядоченно относительно инициализации любого из этих временных объектов или объекта результата.
  • E выполняется до уничтожения любого параметра функции.
(начиная с C++26)

Синтаксис выражения вызова функции похож на синтаксис value initialization T(), выражения приведения в стиле функции T(A1) и прямой инициализации временного объекта T(A1, A2, A3, ...), где T — имя типа.

#include <cstdio>

struct S
{
    int f1(double d)
    {
        return printf("%f \n", d); // variable argument function call
    }
    
    int f2()
    {
        return f1(7); // member function call, same as this->f1()
                      // integer argument converted to double
    }
};

void f()
{
    puts("function called"); // function call
}

int main()
{
    f();    // function call
    S s;
    s.f2(); // member function call
}

Вывод:

function called
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()
{
    // comma is often used to execute more than one expression
    // where the language grammar allows only one expression:
    
    // * in the third component of the for loop
    for (int i = 0, j = 10; i <= j; ++i, --j)
    //            ^list separator      ^comma operator
        std::cout << "i = " << i << " j = " << j << '\n';
    
    // * in a return statement
    // return log("an error!"), -1;
    
    // * in an initializer expression
    // MyClass(const Arg& arg)
    // : member{ throws_if_bad(arg), arg }
    
    // etc.
    
    // comma operators can be chained; the result of the last
    // (rightmost) expression is the result of the whole chain:
    int n = 1;
    int m = (++n, std::cout << "n = " << n << '\n', ++n, 2 * n);
    
    // m is now 6
    std::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

E1 вычисляется и контекстно преобразуется к типу bool; если результат равен true, результатом условного выражения является значение E2 ; в противном случае результатом условного выражения является значение E3 .

Тип и категория значения условного выражения E1 ? E2 : E3 определяются следующим образом:

Этап 1

Если E2 и E3 имеют тип void, результатом является rvalue(до C++11)prvalue(начиная с C++11) типа void.

Если ровно одно из выражений E2 и E3 имеет тип void:

  • Если операнд типа void является (возможно, заключённым в скобки) выражением throw, результат имеет тип и категорию значения другого операнда[1]. Если другой операнд является битовым полем, результат также является битовым полем.
  • В противном случае программа некорректна.

Если ни одно из выражений E2 и E3 не имеет тип void, переходят к следующему этапу.

2 + 2 == 4 ? throw 123 : throw 456; // the result is of type “void”

2 + 2 != 4 ? "OK" : throw "error";  // the result is of type “const char[3]”
                                    // even if an exception is always thrown

Этап 2

Если E2 или E3 являются lvalue-битовыми полями(до C++11)glvalue-битовыми полями одной категории значения(начиная с C++11) типов cv1 T и cv2 T соответственно, то для дальнейших действий считается, что операнды имеют тип cv T, где cv представляет собой объединение cv1 и cv2.

Если E2 и E3 имеют разные типы и выполняется любое из следующих условий, переходят к этапу 3:

  • По крайней мере одно из выражений E2 и E3 имеет тип класса (возможно, с квалификаторами cv).
  • Оба выражения E2 и E3 являются lvalue одного типа(до C++11)glvalue одной категории значения и одного типа(начиная с C++11), за исключением квалификаторов cv.

В противном случае переходят к этапу 4.

Этап 3

Предпринимаются попытки сформировать последовательность неявного преобразования[2] из выражения-операнда X типа TX в целевой тип, связанный с типом TY выражения-операнда Y, следующим образом:

  • Если Y является lvalue, целевым типом является TY&, однако последовательность неявного преобразования может быть сформирована только в том случае, если ссылка могла бы связываться напрямую с lvalue(до C++11)glvalue(начиная с C++11).
  • Если Y является xvalue, целевым типом является TY&&, однако последовательность неявного преобразования может быть сформирована только в том случае, если ссылка могла бы связываться напрямую.
(начиная с C++11)
  • Если Y является rvalue(до C++11)prvalue(начиная с C++11) или если ни одну из указанных выше последовательностей преобразования сформировать не удалось, а по крайней мере одно из выражений TX и TY имеет тип класса (возможно, с квалификаторами cv):
    • Если TX и TY являются одним и тем же типом класса (без учёта квалификаторов cv):
      • Если TY имеет не меньше квалификаторов cv, чем TX, целевым типом является TY.
      • В противном случае последовательность преобразования не формируется.
    • В противном случае, если TY является базовым классом TX, целевым типом является TY с квалификаторами cv типа TX.
    • В противном случае целевым типом является тип Z, где Z — это значение Y после применения стандартных преобразований lvalue-в-rvalue, массива-в-указатель и функции-в-указатель.
  • В противном случае последовательность преобразования не формируется.

С помощью этого процесса определяется, можно ли сформировать последовательность неявного преобразования из E2 в целевой тип, определённый для E3, и наоборот.

  • Если последовательность преобразования сформировать нельзя, переходят к следующему этапу.
  • Если можно сформировать ровно одну последовательность преобразования:
    • Если последовательность преобразования неоднозначна, программа некорректна.
    • В противном случае это преобразование применяется к выбранному операнду, и преобразованный операнд используется вместо исходного в дальнейших действиях; затем переходят к следующему этапу.
  • Если можно сформировать обе последовательности, программа некорректна.
struct A {};

struct B : A {};

using T = const B;

A a = true ? A() : T(); // Y = A(), TY = A, X = T(), TX = const B, Target = const A

Этап 4

Если E2 и E3 являются lvalue одного типа, результатом является lvalue этого типа; он является битовым полем, если хотя бы одно из выражений E2 и E3 является битовым полем.

(до C++11)

Если E2 и E3 являются glvalue одного типа и одной категории значения, результат имеет тот же тип и категорию значения и является битовым полем, если хотя бы одно из выражений E2 и E3 является битовым полем.

(начиная с C++11)

В противном случае результатом является rvalue(до C++11)prvalue(начиная с C++11).

  • Если E2 и E3 имеют разные типы и хотя бы один из них имеет тип класса (возможно, с квалификаторами cv), переходят к этапу 5.
  • В противном случае переходят к этапу 6.

Этап 5

Выполняется разрешение перегрузки с использованием встроенных кандидатов для попытки преобразовать операнды к встроенным типам:

  • Если разрешение перегрузки завершается неудачей, программа некорректна.
  • В противном случае выбранные преобразования применяются, а преобразованные операнды используются вместо исходных в дальнейших действиях. Переходят к следующему этапу.

Этап 6

К (возможно, преобразованным) выражениям E2 и E3 применяются преобразования массива-в-указатель и функции-в-указатель. После этих преобразований должно выполняться хотя бы одно из следующих условий, иначе программа некорректна:

  • E2 и E3 имеют один и тот же тип. В этом случае результат имеет этот тип и копирующе инициализируется выбранным операндом.
  • Оба выражения E2 и E3 имеют арифметический тип или тип перечисления. В этом случае к ним применяются обычные арифметические преобразования, чтобы привести их к общему типу; результат имеет этот тип.
  • По крайней мере один из операндов E2 и E3 является указателем. В этом случае применяются преобразования lvalue-в-rvalue, указатель, указатель на функцию(начиная с C++17) и преобразования квалификации, чтобы привести их к составному типу указателя; результат имеет этот тип.
  • По крайней мере один из операндов E2 и E3 является указателем на член. В этом случае применяются преобразования lvalue-в-rvalue, указатель-на-член, указатель на функцию(начиная с C++17) и преобразования квалификации, чтобы привести их к составному типу указателя; результат имеет этот тип.
  • Оба выражения E2 и E3 являются нулевыми константами указателя, и по крайней мере одно из них имеет тип std::nullptr_t. В этом случае результат имеет тип std::nullptr_t.
(начиная с C++11)
int* intPtr;

using Mixed = decltype(true ? nullptr : intPtr);

static_assert(std::is_same_v<Mixed, int*>); // nullptr becoming int*

struct A
{
    int* m_ptr;
} a;

int* A::* memPtr = &A::m_ptr; // memPtr is a pointer to member m_ptr of A

// memPtr makes nullptr as type of pointer to member m_ptr of A
static_assert(std::is_same_v<decltype(false ? memPtr : nullptr), int*A::*>);

// a.*memPtr is now just pointer to int and nullptr also becomes pointer to int
static_assert(std::is_same_v<decltype(false ? a.*memPtr : nullptr), int*>);
  1. Такой условный оператор часто использовался в C++11 для программирования с constexpr до появления C++14.
  2. Доступ к члену, а также сведения о том, удалена ли функция преобразования(начиная с C++11) и является ли операнд битовым полем, не учитываются.

Тип результата условного оператора также доступен как бинарный признак типа std::common_type.

(начиная с C++11)

Перегрузки

Для каждой пары повышенных арифметических типов L и R и для каждого типа P, где P является указателем, указателем на член или перечислением с областью видимости, в разрешении перегрузки участвуют следующие сигнатуры функций:

LR operator?:(bool, L, R);
P operator?:(bool, P, P);

где LR — результат применения обычных арифметических преобразований к L и R.

Оператор «?:» нельзя перегрузить; эти сигнатуры функций существуют только для целей разрешения перегрузки.

#include <iostream>
#include <string>

struct Node
{
    Node* next;
    int data;
    
    // deep-copying copy constructor
    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()
{   
    // simple rvalue example
    int n = 1 > 2 ? 10 : 11;  // 1 > 2 is false, so n = 11
    
    // simple lvalue example
    int m = 10; 
    (n == m ? n : m) = 7; // n == m is false, so m = 7
    
    //output the result
    std::cout << "n = " << n << "\nm = " << m;
}

Вывод:

n = 11
m = 7

Стандартная библиотека

Многие классы стандартной библиотеки перегружают operator() для использования в качестве функциональных объектов.

operator()
удаляет объект или массив
(открытая функция-член std::default_delete<T>)
operator()
возвращает сумму двух аргументов
(открытая функция-член std::plus<T>)
operator()
возвращает разность двух аргументов
(открытая функция-член std::minus<T>)
operator()
возвращает произведение двух аргументов
(открытая функция-член std::multiplies<T>)
operator()
возвращает результат деления первого аргумента на второй
(открытая функция-член std::divides<T>)
operator()
возвращает остаток от деления первого аргумента на второй
(открытая функция-член std::modulus<T>)
operator()
возвращает отрицание аргумента
(открытая функция-член std::negate<T>)
operator()
проверяет, равны ли аргументы
(открытая функция-член std::equal_to<T>)
operator()
проверяет, не равны ли аргументы
(открытая функция-член std::not_equal_to<T>)
operator()
проверяет, больше ли первый аргумент второго
(открытая функция-член std::greater<T>)
operator()
проверяет, меньше ли первый аргумент второго
(открытая функция-член std::less<T>)
operator()
проверяет, больше ли первый аргумент второго или равен ему
(открытая функция-член std::greater_equal<T>)
operator()
проверяет, меньше ли первый аргумент второго или равен ему
(открытая функция-член std::less_equal<T>)
operator()
возвращает результат логической операции И над двумя аргументами
(открытая функция-член std::logical_and<T>)
operator()
возвращает результат логической операции ИЛИ над двумя аргументами
(открытая функция-член std::logical_or<T>)
operator()
возвращает результат логической операции НЕ над аргументом
(открытая функция-член std::logical_not<T>)
operator()
возвращает результат побитовой операции И над двумя аргументами
(открытая функция-член std::bit_and<T>)
operator()
возвращает результат побитовой операции ИЛИ над двумя аргументами
(открытая функция-член std::bit_or<T>)
operator()
возвращает результат побитовой операции исключающего ИЛИ над двумя аргументами
(открытая функция-член std::bit_xor<T>)
operator()
возвращает логическое дополнение результата вызова сохранённого предиката
(открытая функция-член std::unary_negate<Predicate>)
operator()
возвращает логическое дополнение результата вызова сохранённого предиката
(открытая функция-член std::binary_negate<Predicate>)
operator()
вызывает сохранённую функцию
(открытая функция-член std::reference_wrapper<T>)
operator()
вызывает целевой объект
(открытая функция-член std::function<R(Args...)>)
operator()
вызывает целевой объект
(открытая функция-член std::move_only_function)
operator()
вызывает целевой объект
(открытая функция-член std::copyable_function)
operator()
возобновляет выполнение сопрограммы
(открытая функция-член std::coroutine_handle<Promise>)
operator()
сравнивает две строки в лексикографическом порядке с помощью фасета collate данной локали
(открытая функция-член std::locale)
operator()
сравнивает два значения типа value_type
(открытая функция-член std::map<Key,T,Compare,Allocator>::value_compare)
operator()
сравнивает два значения типа value_type
(открытая функция-член std::multimap<Key,T,Compare,Allocator>::value_compare)
operator()
выполняет функцию
(открытая функция-член std::packaged_task<R(Args...)>)
operator()
продвигает состояние генератора и возвращает сгенерированное значение
(открытая функция-член std::linear_congruential_engine<UIntType,a,c,m>)
operator()
(C++11)
генерирует следующее случайное число из распределения
(открытая функция-член std::uniform_int_distribution<IntType>)

Оператор запятая не перегружен ни одним классом стандартной библиотеки. Библиотека boost использует operator, в boost.assign, boost.spirit и других библиотеках. Библиотека доступа к базам данных SOCI также перегружает operator,.

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применён к Поведение согласно опубликованному стандарту Корректное поведение
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 выражение function в выражении вызова функции
могло быть lvalue-указателем на функцию
не допускается
CWG 1805 C++98 при определении целевого типа для последовательности неявного преобразования
не был ясен способ преобразования Y в Z
прояснено
CWG 1895 C++98
C++11
не было ясно, препятствует ли преобразованию в
условных выражениях удалённая (C++11) или недоступная (C++98)
функция преобразования; также не рассматривались преобразования из базового
класса в prvalue производного класса
обрабатывается так же,
как разрешение перегрузки
CWG 1932 C++98 поля битов одного типа не учитывались в условных выражениях обрабатываются с учётом базовых типов
CWG 2226 C++11 при определении целевого типа другого
операнда условного оператора ссылка не могла
привязываться к xvalue, если этот операнд является lvalue
разрешено
CWG 2283 C++17 требование полноты типа для оператора вызова функции
было случайно удалено документом P0135R1
требование восстановлено
CWG 2321 C++98 при определении целевого типа другого операнда
условного оператора тип производного класса нельзя было
преобразовать в тип базового класса с меньшим числом cv-квалификаторов
разрешено преобразование к типу базового
класса с cv-квалификацией
операнда производного класса
CWG 2715 C++98 инициализация и уничтожение каждого
параметра происходили бы в контексте
вызывающей функции, которой может не существовать[1]
происходит в контексте
охватывающего полного выражения
CWG 2850 C++98 порядок уничтожения параметров был неясен прояснён
CWG 2865 C++98 если TX и TY имеют один и тот же тип класса, а TX
имеет более строгую cv-квалификацию, чем TY, последовательность неявного преобразования
всё ещё могла быть сформирована из prvalue Y
в этом случае последовательность
преобразования не формируется
CWG 2906 C++98 преобразования lvalue в rvalue безусловно применялись
в случае результата rvalue условного оператора
применяются только в некоторых случаях
  1. Например, функции можно вызывать в инициализаторе переменной в области пространства имён; в этом контексте «вызывающей функции» нет.

См. также

Приоритет операторов
Перегрузка операторов

Распространённые операторы
Присваивание Инкремент,
декремент
Арифметические Логические Сравнения Доступ к
членам
Другие
a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b
++a
--a
a++
a--
+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b
!a
a && b
a || b
a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b
a[...]
*a
&a
a->b
a.b
a->*b
a.*b
вызов функции

a(...)
запятая

a, b
условный оператор

a ? b : c
Специальные операторы

static_cast преобразует тип в другой связанный тип
dynamic_cast выполняет преобразование в иерархиях наследования
const_cast добавляет или удаляет cv-квалификаторы
reinterpret_cast преобразует тип в несвязанный тип
Приведение в стиле C преобразует один тип в другой с помощью сочетания static_cast, const_cast и reinterpret_cast
new создаёт объекты с динамической длительностью хранения
delete уничтожает объекты, ранее созданные выражением new, и освобождает полученную область памяти
sizeof запрашивает размер типа
sizeof... запрашивает размер пакета (начиная с C++11)
typeid запрашивает информацию о типе
noexcept проверяет, может ли выражение выбросить исключение (начиная с C++11)
alignof запрашивает требования к выравниванию типа (начиная с C++11)
^^ создаёт значение отражения на основе грамматической конструкции (начиная с C++26)

Документация по C для Другие операторы

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/operator_other

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API