& в const T& или T& в volatile T&.
[2] Сопоставление со стандартными преобразованиями основных типов. Из последовательностей, не относящихся к [1], наиболее предпочтительны те, которые содержат только стандартные целочисленные преобразования (§R.4.1), преобразования float в double и тривиальные преобразования.
[3] Сопоставление с любыми стандартными преобразованиями. Из последовательностей, не относящихся к [2], наиболее предпочтительны те, которые содержат только любые стандартные преобразования (§R.4.1, §R.4.2, §R.4.3, §R.4.4, §R.4.5, §R.4.6, §R.4.7, §R.4.8) и тривиальные преобразования. Для этих последовательностей если A является прямым или косвенным общим базовым для класса B, то преобразование B* в A* предпочтительнее преобразования B* в void* или const void*. Далее, если B является прямым или косвенным базовым классом для C, то предпочтительнее преобразование C* в B*, чем C* в A*, и предпочтительнее преобразование C& в B&, чем C& в A&. Иерархия классов выступает здесь критерий отбора преобразований указателя в член (§R.4.8).
[4] Сопоставление с пользовательскими преобразованиями. Из последовательностей, не относящихся к [3], наиболее предпочтительны те, которые содержат только пользовательские (§R.12.3), стандартные (§R.4) и тривиальные преобразования.
[5] Сопоставление с эллипсисом. Последовательности, которые требуют сопоставления с эллипсисом, считаются наименее предпочтительными.
Пользовательские преобразования выбирают, исходя из типа переменной, которая инициализируется или которой присваивается значение.
class Y {
//…
public:
operator int();
operator double();
};
void f(Y y)
{
int i = y; // вызов Y::operator int()
double d;
d = y; // вызов Y::operator double()
float f = y; // ошибка: неоднозначность
}
Стандартные преобразования (§R.4) могут применяться к параметру, как до пользовательского преобразования, так и после него.
struct S { S(long); operator int();} ;
void f(long), f(char*);
void g(S), g(char*);
void h(const S&), h(char*);
void k(S& a)
{
f(a); // f(long(a.operator int()))
g(1); // g(S(long(1)))
h(1); // h(S(long(1)))
}
Если для параметра требуется пользовательское преобразование, то не учитываются никакие стандартные преобразования, которые могут затрагивать этот параметр, например:
class x {
public:
x(int);
};
class y {
public:
y(long);
};
void f(x);
void f(y);
void g()
{
f(1); // неоднозначность
}
Здесь вызов f(1) неоднозначен. Несмотря на то, что для вызова f(y(long(1))) требуется на одно стандартное преобразование больше, чем для вызова f(x(1)), второй вызов не является предпочтительным.
Преобразования с помощью конструктора (§R.12.1) и с помощью функции преобразования (§R.12.3.2) равноправны.
struct X {
operator int();
};
struct Y {
Y(X);
};
Y operator+(Y,Y);
void f(X a, X b)
{
a+b; // ошибка, неоднозначность:
// operator+(Y(a), Y(b)) или
// a.operator int() + b.operator int()
}
R.13.3 Адрес перегруженной функции
Когда функция с некоторым именем используется без параметров, среди всех функций с таким именем в текущей области видимости выбирается единственная, которая точно соответствует назначению. Назначением может быть:
• инициализируемый объект (§R.8.4);
• левая часть операции присваивания (§R.5.17);
• формальный параметр функции (§R.5.2.2);
• формальный параметр пользовательской операции (§R.13.4);
• тип значения, возвращаемого функцией (§R.8.2.5).
Отметим, что если f() и g() являются перегруженными функциями, то для правильной интерпретации f(&g) или эквивалентного выражения f(g) нужно рассмотреть пересечение множеств выбора для f() и g(). Приведем пример:
int f(double);
int f(int);
int (*pfd)(double) = &f;
int (*pfi)(int) = &f;
int (*pfe)(…) = &f; // ошибка: несоответствие типов
Последняя инициализация ошибочна, не из-за неоднозначности, а потому, что не определено ни одной функции f() типа int(…).
Отметим, что не существует никакого стандартного преобразования (§R.4) указателя на функцию одного типа в указатель на функцию другого типа (§R.4.6). В частности, даже если B является общим базовым классом D, две следующие инициализации недопустимы:
D* f();
B* (*p1)() =&f; // ошибка
void g(D*);
void (*p2)(B*) =&g; // ошибка
R.13.4 Перегруженные операции
Перегружать можно большинство операций.
имя-функции-оператор:
operator операция
операция: один из
new delete
+ - * / % ^ & | ~
! = ‹ › += -= *= /= %=
^= &= |= ‹‹ ›› ››= ‹‹= == !=
‹= ›= && || ++ -- , -›* -›
() []
Две последние операции - это вызов функции (§R.5.2.2) и индексация (§R.5.2.1).
Можно перегружать следующие (как бинарные, так и унарные) операции:
+ - * &
Нельзя перегружать следующие операции:
. .* :: ?: sizeof
а также и специальные символы препроцессора # и ## (§R.16).
Обычно функции, задающие операции (функция-оператор) не вызываются явно, к ним обращаются для выполнения операций (§R.13.4.1, §R.13.4.2).
Однако, к ним можно обращаться явно, например:
complex z = a.operator+(b); // complex z = a+b
void* p = operator new(sizeof(int)*n);
Операции new и delete описаны в §R.5.3.3 и §R.5.3.4 и к ним не относятся перечисляемые ниже правила.
Функция-оператор может быть функцией-членом или иметь по крайней мере один параметр типа класс или ссылка на класс. Нельзя изменить приоритет, порядок выполнения или число операндов операции, но можно изменить предопределенное назначение таких операций: =, унарная & и ,(запятой), если они применяются к объекту типа класс. За исключением функции operator=(), функция-оператор наследуется. Правила для operator=() даны в §R.12.8.
Эквивалентность некоторых операций над основными типами (например, ++a эквивалентно a+=1) может не сохраняться для таких же операций над классами. Для некоторых операций требуется, чтобы в случае использования основных типов операнд был адресом (например, для +=). Это требование может быть снято, если операция задана над классами.
Перегруженная операция не может иметь стандартные значения параметров (§R.8.2.6).
Операции, которые явно не указаны в §R.13.4.3-§R.13.4.7, действуют как обычные унарные или бинарные операции, подчиняющиеся правилам, приведенным в §R.13.4.1 или §R.13.4.2.
R.13.4.1 Унарные операции
Префиксную унарную операцию можно задать с помощью нестатической функции-члена (§R.9.3), без параметров или с помощью функции, не являющейся членом, с одним параметром. Таким образом, для всякой префиксной унарной операции @, выражение @x может интерпретироваться как x.operator@() или как operator@(x). Если описаны функции-операторы обоих видов, то какая из них будет использоваться при вызове, определяется правилами сопоставления параметров (§R.13.2). Постфиксные унарные операции, такие как ++ и --, объясняются в §R.13.4.7.
R.13.4.2 Бинарные операции
Бинарную операцию можно задать с помощью нестатической функции-члена (§R.9.3), имеющей один параметр, или с помощью функции, не являющейся членом, с двумя параметрами. Таким образом, для всякой бинарной операции @ выражение x@y может интерпретироваться как x.operator@(y) или как operator@(x,y). Если описаны функции-операторы обоих видов, то какая из них будет использоваться при вызове, определяется правилами сопоставления параметров (§R.13.2).
R.13.4.3 Присваивания
Функция присваивания operator=() должна быть нестатической функцией-членом. Она не наследуется (§R.12.8). Более того, если пользователь не определил для класса X функцию operator=, то используется стандартная функция operator=, которая определяется как присваивание по членам для класса X.
X& X::operator=(const X& from)
{
// копирование по членам X
}
R.13.4.4 Вызов функции
Вызов функции есть конструкция вида:
первичное-выражение ( список-выражений opt )
Она считается бинарной операцией, в которой первичное-выражение представляет первый операнд, а список-выражений (возможно пустой), - второй операнд. Именем, задающим функцию, служит operator(), и вызов x(arg1,arg2,arg3) для объекта класса x интерпретируется как x.operator()(arg1,arg2,arg3). Функция operator() должна быть нестатической функцией-членом класса x.
R.13.4.5 Индексация
Индексация, определяемая как:
первичное-выражение [ выражение ]
считается бинарной операцией. Выражение с индексацией x[y] для объекта класса x интерпретируется как x.operator[](y). Функция operator[] должна быть нестатической функцией-членом класса x.
R.13.4.6 Доступ к члену класса
Доступ к члену класса определяется с помощью операции -›:
первичное-выражение -› первичное-выражение
Он считается унарной операцией. Для объекта класса x выражение x-›m интерпретируется как (x.operator-›())-›m. Отсюда следует, что функция operator-›() должна возвращать или указатель на класс, или ссылку на класс, или объект класса, для которого определена функция operator-›(). Она должна быть нестатической функцией-членом класса.
R.13.4.7 Инкремент и декремент
Функция с именем operator++ и с одним параметром задает для объектов некоторого класса операцию префиксного инкремента ++. Функция с именем operator++ и с двумя параметрами задает для объектов некоторого класса операцию постфиксного инкремента ++. Для постфиксной операции ++ второй параметр должен быть типа int, и, когда в выражении встречается операция постфиксного инкремента, функция operator++ вызывается со вторым параметром, равным нулю. Приведем пример:
class X {
public:
X operator++(); // префиксная ++a
X operator++(int) // постфиксная a++
};
void f(X a)
{
++a; // a.operator++();
a++; // a.operator++(0);
a.operator++(); // явный вызов: действует как ++a;
a.operator++(0); // явный вызов: действует как a++;
}
Префиксные и постфиксные операции декремента - определяются аналогичным образом.
R.14 ШАБЛОНЫ ТИПА