Spec-Zone.ru › C++

Явное преобразование типов

Преобразует значения между типами с помощью сочетания явных и неявных преобразований.

Синтаксис

( type-id ) unary-expression (1)
simple-type-specifier ( expression-list (необязательно) )
simple-type-specifier ( initializer-list (необязательно) )
(2) (до C++11)
(начиная с C++11)
simple-type-specifier { initializer-list (необязательно) } (3) (начиная с C++11)
simple-type-specifier { designated-initializer-list } (4) (начиная с C++20)
typename identifier ( initializer-list (необязательно) ) (5) (начиная с C++11)
typename identifier { initializer-list (необязательно) } (6) (начиная с C++11)
typename identifier { designated-initializer-list } (7) (начиная с C++20)

Явно преобразует любое количество значений в значение целевого типа.

1) Явное преобразование типов (запись в форме приведения), также называемое приведением в стиле C.
2-7) Явное преобразование типов (функциональная запись), также называемое приведением в функциональном стиле.
type-id - идентификатор типа
unary-expression - унарное выражение (оператор верхнего уровня которого не имеет приоритета выше, чем у приведения в стиле C)
simple-type-specifier - спецификатор простого типа
expression-list - список выражений, разделённых запятыми (за исключением выражений с оператором-запятой без скобок)
initializer-list - список инициализирующих выражений, разделённых запятыми
designated-initializer-list - список именованных инициализирующих выражений, разделённых запятыми
identifier - идентификатор (возможно, квалифицированный), включая идентификаторы шаблонов

Пояснение

1) При встрече приведения в стиле C компилятор пытается интерпретировать его как следующие выражения приведения, в указанном порядке:
a) const_cast<type-id >(unary-expression );
b) static_cast<type-id >(unary-expression ), с расширениями: указатель или ссылка на производный класс дополнительно может быть приведён к указателю или ссылке на однозначный базовый класс (и наоборот), даже если базовый класс недоступен (то есть это приведение игнорирует спецификатор закрытого наследования). То же относится к приведению указателя на член к указателю на член однозначного невиртуального базового класса;
c) static_cast (с расширениями), за которым следует const_cast;
d) reinterpret_cast<type-id >(unary-expression );
e) reinterpret_cast, за которым следует const_cast.
Выбирается первый вариант, удовлетворяющий требованиям соответствующего оператора приведения, даже если он некорректен (см. пример). Если используется static_cast, за которым следует const_cast, и преобразование допускает несколько таких интерпретаций, оно некорректно.
Кроме того, приведения в стиле C могут выполняться из указателей на неполный тип класса, в такие указатели и между ними. Если и type-id, и тип unary-expression являются указателями на неполные типы классов, не определено, будет ли выбран static_cast или reinterpret_cast.
2-7) Приведение в функциональном стиле задаёт тип (simple-type-specifier  или identifier (начиная с C++11)) и инициализатор (оставшиеся части); оно создаёт значение целевого типа T, определяемого заданным типом и инициализатором(начиная с C++17):

T — это заданный тип.

(до C++17)

T определяется следующим образом:

  • Если заданный тип является заглушкой для выводимого типа класса, T — это тип возвращаемого значения функции, выбранной разрешением перегрузки для вывода аргументов шаблона класса.
  • В противном случае, если заданный тип содержит заглушечный тип, T — это выведенный тип.
(начиная с C++23)
  • В противном случае T — это заданный тип.
(начиная с C++17)
Результат преобразования определяется следующим образом:
  • Если приведение в функциональном стиле имеет синтаксис (2) и в скобках находится ровно одно выражение, это приведение эквивалентно соответствующему приведению в стиле C.
  • В противном случае, если T является (возможно, cv-квалифицированным) типом void, результатом является rvalue(до C++11)prvalue(начиная с C++11) типа void, при этом инициализация не выполняется.
  • Если инициализатор не является (), программа некорректна.
(до C++11)
  • Если инициализатор после раскрытия пакета (если оно есть) не является () или {}, программа некорректна.
(начиная с C++11)
  • В противном случае, если T является ссылочным типом, приведение в функциональном стиле имеет тот же эффект, что и прямая инициализация вымышленной переменной t типа T заданным инициализатором; результатом является инициализированная t.
  • Результатом является lvalue.
(до C++11)
  • Если T является типом lvalue-ссылки или rvalue-ссылки на функцию, результатом является lvalue.
  • В противном случае результатом является xvalue.
(начиная с C++11)
  • В противном случае результатом является rvalue(до C++11)prvalue(начиная с C++11) типа T , обозначающим временный объект(до C++17), объектом-результатом которого является(начиная с C++17) объект, прямо инициализированный заданным инициализатором.

Разрешение неоднозначностей

Неоднозначная инструкция-объявление

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

struct M {};
struct L { L(M&); };
 
M n;
void f()
{
    M(m);    // declaration, equivalent to M m;
    L(n);    // ill-formed declaration, equivalent to L n;
    L(l)(m); // still a declaration, equivalent to L l((m));
}

Однако, если внешний декларатор в неоднозначной инструкции-объявлении содержит возвращаемый тип в конце, инструкция считается инструкцией-объявлением только в том случае, если возвращаемый тип в конце начинается с auto:

struct M;

struct S
{
    S* operator()();
    int N;
    int M;
    
    void mem(S s)
    {
        auto(s)()->M; // expression (S::M hides ::M), invalid before C++23
    }
};

void f(S s)
{
    {
        auto(s)()->N; // expression, invalid before C++23
        auto(s)()->M; // function declaration, equivalent to M s();
    }
    {
        S(s)()->N;    // expression
        S(s)()->M;    // expression
    }
}
(начиная с C++11)

Неоднозначный параметр функции

Описанная выше неоднозначность может возникнуть и в контексте объявления. В этом случае выбор производится между объявлением объекта с выражением приведения в функциональном стиле в качестве инициализатора и объявлением с декларатором функции, в котором вокруг имени параметра избыточно используются скобки. Неоднозначность разрешается так же: любую конструкцию, например потенциальное объявление параметра, которая может быть объявлением, следует считать объявлением:

struct S
{
    S(int);
};

void foo(double a)
{
    S w(int(a)); // function declaration: has a parameter `a` of type int
    S x(int());  // function declaration: has an unnamed parameter of type int(*)() 
                 // that is adjusted from int()
    
    // Ways to avoid ambiguity:
    S y((int(a))); // object declaration: extra pair of parentheses
    S y((int)a);   // object declaration: C-style cast
    S z = int(a);  // object declaration: no ambiguity for this syntax
}

Однако, если внешний декларатор в неоднозначном объявлении параметра содержит возвращаемый тип в конце, неоднозначность разрешается в пользу объявления только в том случае, если он начинается с auto:

typedef struct BB { int C[2]; } *B, C;

void foo()
{
    S a(B()->C);    // object declaration: B()->C cannot declare a parameter
    S b(auto()->C); // function declaration: has an unnamed parameter of type C(*)()
                    // that is adjusted from C()
}
(начиная с C++11)

Неоднозначный идентификатор типа

Неоднозначность может возникнуть из-за сходства приведения в функциональном стиле с идентификатором типа. Она разрешается так: любую конструкцию, которая в своём синтаксическом контексте может быть идентификатором типа, следует считать идентификатором типа:

// `int()` and `int(unsigned(a))` can both be parsed as type-id:
// `int()`            represents a function returning int
//                    and taking no argument
// `int(unsigned(a))` represents a function returning int
//                    and taking an argument of type unsigned
void foo(signed char a)
{
    sizeof(int());            // type-id (ill-formed)
    sizeof(int(a));           // expression
    sizeof(int(unsigned(a))); // type-id (ill-formed)
    
    (int()) + 1;            // type-id (ill-formed)
    (int(a)) + 1;           // expression
    (int(unsigned(a))) + 1; // type-id (ill-formed)
}

Однако, если внешний abstract-declarator в неоднозначном идентификаторе типа содержит возвращаемый тип в конце, неоднозначность разрешается в пользу идентификатора типа только в том случае, если он начинается с auto:

typedef struct BB { int C[2]; } *B, C;

void foo()
{
    sizeof(B()->C[1]);    // OK, sizeof(expression)
    sizeof(auto()->C[1]); // error: sizeof of a function returning an array
}
(начиная с C++11)

Примечания

Макрос проверки поддержки функции Значение Стандарт Функция
__cpp_auto_cast 202110L (C++23) auto(x) и auto{x}

Пример

#include <cassert>
#include <iostream>

double f = 3.14;
unsigned int n1 = (unsigned int)f; // C-style cast
unsigned int n2 = unsigned(f);     // function-style cast

class C1;
class C2;
C2* foo(C1* p)
{
    return (C2*)p; // casts incomplete type to incomplete type
}

void cpp23_decay_copy_demo()
{
    auto inc_print = [](int& x, const int& y)
    {
        ++x;
        std::cout << "x:" << x << ", y:" << y << '\n';
    };
    
    int p{1};
    inc_print(p, p); // prints x:2 y:2, because param y here is an alias of p
    int q{1};
    inc_print(q, auto{q}); // prints x:2 y:1, auto{q} (C++23) casts to prvalue,
                           // so the param y is a copy of q (not an alias of q)
}

// In this example, C-style cast is interpreted as static_cast
// even though it would work as reinterpret_cast
struct A {};
struct I1 : A {};
struct I2 : A {};
struct D : I1, I2 {};

int main()
{
    D* d = nullptr;
//  A* a = (A*)d;                   // compile-time error
    A* a = reinterpret_cast<A*>(d); // this compiles
    assert(a == nullptr);
    
    cpp23_decay_copy_demo();
}

Вывод:

x:2 y:2
x:2 y:1

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

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

DR Применён к Опубликованное поведение Корректное поведение
CWG 1223
(P2915R0)
C++11 добавление возвращаемого типа в конце привело к появлению новых неоднозначностей устраняет их
CWG 1893 C++11 при приведении в функциональном стиле не учитывалось раскрытие пакетов учитывает его
CWG 2351 C++11 void{} было некорректным сделано корректным
CWG 2620 C++98 разрешение неоднозначности для параметров функции
могло быть истолковано неверно
формулировка улучшена
CWG 2828 C++98 приведение в стиле C было некорректным, если существовало несколько интерпретаций
static_cast, за которым следует const_cast,
независимо от того, использовались ли эти преобразования фактически
учитываются только
преобразования,
которые могут быть использованы
CWG 2894 C++98 приведения в функциональном стиле могли создавать rvalue-ссылки могут создавать только lvalue-ссылки

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2024):
    • 7.6.1.4 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 7.6.3 Явное преобразование типов (запись в форме приведения) [expr.cast]
  • Стандарт C++20 (ISO/IEC 14882:2020):
    • 7.6.1.4 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 7.6.3 Явное преобразование типов (запись в форме приведения) [expr.cast]
  • Стандарт C++17 (ISO/IEC 14882:2017):
    • 8.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 8.4 Явное преобразование типов (запись в форме приведения) [expr.cast]
  • Стандарт C++14 (ISO/IEC 14882:2014):
    • 5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 5.4 Явное преобразование типов (запись в форме приведения) [expr.cast]
  • Стандарт C++11 (ISO/IEC 14882:2011):
    • 5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 5.4 Явное преобразование типов (запись в форме приведения) [expr.cast]
  • Стандарт C++03 (ISO/IEC 14882:2003):
    • 5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 5.4 Явное преобразование типов (запись в форме приведения) [expr.cast]
  • Стандарт C++98 (ISO/IEC 14882:1998):
    • 5.2.3 Явное преобразование типов (функциональная запись) [expr.type.conv]
    • 5.4 Явное преобразование типов (запись в форме приведения) [expr.cast]

См. также

const_cast преобразование добавляет или удаляет const
static_cast преобразование выполняет базовые преобразования
dynamic_cast преобразование выполняет проверяемые полиморфные преобразования
reinterpret_cast преобразование выполняет общие низкоуровневые преобразования
стандартные преобразования неявные преобразования из одного типа в другой
Документация по C для оператора приведения

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

Spec-Zone.ru

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