Спецификаторы, которые можно использовать в объявлении:
decl-specifier: storage-class-specifier defining-type-specifier function-specifier friend typedef constexpr inline
decl-specifier-seq: decl-specifier attribute-specifier-seqopt decl-specifier decl-specifier-seq
Необязательный параметр attribute-specifier-seqв a decl-specifier-seq относится к типу, определяемому предшествующим decl-specifiers ([dcl.meaning]). Параметр attribute-specifier-seq влияет на тип только для объявления, в котором он появляется, а не для других объявлений, связанных с тем же типом.
Каждый из них decl-specifierдолжен появляться не более одного раза в целом decl-specifier-seq, за исключением того, что long может появляться дважды.
Если type-nameвстречаются при синтаксическом анализе decl-specifier-seq, он интерпретируется как часть decl-specifier-seqтогда и только тогда , когда нет предыдущего defining-type-specifierдругой , чем cv-qualifierв decl-specifier-seq. Последовательность должна быть согласованной, как описано ниже. [ Example:
typedef char* Pc;
static Pc; // error: name missing
Здесь объявление static Pc неправильно сформировано, потому что для статической переменной типа не указано имя Pc. Чтобы вызвать переменную Pc, должен присутствовать type-specifier(кроме const или volatile), чтобы указать, что typedef-name Pc это имя (повторно) объявляется, а не является частью decl-specifierпоследовательности. Другой пример:
void f(const Pc); // void f(char* const) (not const char*) void g(const int Pc); // void g(const int)
— end example ]
[ Note: Так как signed, unsigned, long, и , short по умолчанию подразумевает int, A type-nameпоявляться после того, как один из этих спецификаторов трактуются как имя существа (ре) объявлено. [ Example:
void h(unsigned Pc); // void h(unsigned int) void k(unsigned int Pc); // void k(unsigned int)
— end example ] ] — end note
Спецификаторы класса хранения:
storage-class-specifier: static thread_local extern mutable
Максимум один storage-class-specifierдолжен появиться в данном задании decl-specifier-seq, за исключением того, что thread_local может появиться с помощью static или extern. Если он thread_local появляется в любом объявлении переменной, он должен присутствовать во всех объявлениях этого объекта. Если a storage-class-specifier появляется в a decl-specifier-seq, в нем не может быть typedef спецификатора, decl-specifier-seqи init-declarator-listor member-declarator-list объявления не должно быть пустым (за исключением анонимного объединения, объявленного в именованном пространстве имен или в глобальном пространстве имен, которое должно быть объявлено static ([class.union.anon])). storage-class-specifierОтносится к имени , объявленному каждому init-declaratorв списке , а не какие - либо имена , объявленных другими спецификаторами. storage-class-specifier Кроме thread_local не должно быть указано в explicit specialization или в explicit instantiation директиве.
[ Note: Переменная, объявленная без storage-class-specifier области действия блока или объявленная как параметр функции, automatic storage duration по умолчанию. ] — end note
Спецификатор thread_local указывает, что названный объект имеет thread storage duration. Он должен применяться только к именам переменных пространства имен или области блока и к именам статических элементов данных. Когда thread_local применяется к переменной области блока storage-class-specifier static , подразумевается, если storage-class-specifierв decl-specifier-seq.
Спецификатор static может применяться только к именам переменных и функций и к anonymous unions. Внутриstatic блока не может быть ни объявлений функций, ни каких-либо static параметров функции. static Спецификатор используется в объявлении переменной объявляет переменную , чтобы static storage duration, если не будет сопровождаться thread_local спецификатора, который объявляет переменную иметь thread storage duration. static Спецификатор можно использовать в объявлениях членов класса; [class.static] описывает его эффект. О связи имени, объявленного с помощью static спецификатора, см [basic.link].
Спецификатор extern можно применять только к именам переменных и функций. Спецификатор extern нельзя использовать в объявлении членов класса или параметров функции. О связи имени, объявленного с помощью спецификатора, см . [ Ключевое слово также может быть использовано в и , но это не в таких условиях. ] extern [basic.link] Note: extern explicit-instantiations linkage-specificationsstorage-class-specifier — end note
Связи, подразумеваемые последовательными декларациями для данного объекта, должны быть согласованы. То есть в пределах заданной области каждое объявление, объявляющее одно и то же имя переменной или одну и ту же перегрузку имени функции, должно подразумевать одну и ту же связь. Однако каждая функция в данном наборе перегруженных функций может иметь разные связи. [ Example:
static char* f(); // f() has internal linkage char* f() // f() still has internal linkage { /* ... */ } char* g(); // g() has external linkage static char* g() // error: inconsistent linkage { /* ... */ } void h(); inline void h(); // external linkage inline void l(); void l(); // external linkage inline void m(); extern void m(); // external linkage static void n(); inline void n(); // internal linkage static int a; // a has internal linkage int a; // error: two definitions static int b; // b has internal linkage extern int b; // b still has internal linkage int c; // c has external linkage static int c; // error: inconsistent linkage extern int d; // d has external linkage static int d; // error: inconsistent linkage
— end example ]
В объявлении можно использовать имя объявленного, но неопределенного класса extern . Такое объявление может использоваться только способами, не требующими полного типа класса. [ Example:
struct S; extern S a; extern S f(); extern void g(S); void h() { g(a); // error: S is incomplete f(); // error: S is incomplete }
— end example ]
Спецификатор mutable должен появляться только в объявлении объекта non-static data member , тип которого не является ни константным, ни ссылочным типом. [ Example:
class X { mutable const int* p; // OK mutable int* const q; // ill-formed };
— end example ]
Спецификатор mutable члена данных класса обнуляет const спецификатор, примененный к содержащему объекту классу, и разрешает модификацию изменяемого члена класса, даже если остальной частью объекта является const ([dcl.type.cv]).
Function-specifiers может использоваться только в объявлениях функций.
function-specifier: virtual explicit
Спецификатор virtual должен использоваться только в начальном объявлении нестатической функции-члена класса; см [class.virtual].
Спецификатор explicit должен использоваться только в объявлении конструктора или функции преобразования в определении его класса; видеть [class.conv.ctor] и [class.conv.fct].
Объявления, содержащие decl-specifier typedef идентификаторы объявления, которые можно использовать позже для именования fundamental или compound типов. Спецификатор typedef не должен сочетаться в a decl-specifier-seqс любым другим типом спецификатора, кроме a defining-type-specifier, и он не должен использоваться ни в decl-specifier-seqa, parameter-declarationни в decl-specifier-seqa function-definition([dcl.fct.def]). Если typedef спецификатор появляется в объявлении без символа declarator, программа имеет неправильный формат.
typedef-name: identifier
Имя, объявленное с помощью typedef спецификатора, становится typedef-name. В рамках своего объявления a typedef-nameсинтаксически эквивалентен ключевому слову и называет тип, связанный с идентификатором, способом, описанным в пункте [dcl.decl]. Таким образом, А является синонимом другого типа. A не вводит новый тип, как это делает объявление класса ( ) или объявление перечисления. [ Послеtypedef-nametypedef-name[class.name] Example:
typedef int MILES, *KLICKSP;
конструкции
MILES distance; extern KLICKSP metricp;
все правильные объявления; тип distance is int и тип metricp is «указатель на int». ] — end example
A typedef-nameтакже может быть введено через alias-declaration. identifierВслед за using ключевым словом становится typedef-name и опциональным attribute-specifier-seqследуя identifierвходит в этот typedef-name. Такой объект typedef-nameимеет такую же семантику, как если бы он был введен typedef спецификатором. В частности, он не определяет новый тип. [ Example:
using handler_t = void (*)(int); extern handler_t ignore; extern void (*ignore)(int); // redeclare ignore using cell = pair<void*, cell*>; // ill-formed
— end example ] defining-type-specifier-seq Из defining-type-idне определяет класс или перечисление , если alias-declaration это declarationиз template-declaration.
В данной области, не относящейся к классу, typedef спецификатор может использоваться для переопределения имени любого типа, объявленного в этой области, для ссылки на тип, на который он уже ссылается. [ Example:
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
— end example ]
В заданной области действия класса typedef спецификатор может использоваться для переопределения любого class-nameобъявленного в этой области действия, который не является также typedef-nameссылкой на тип, на который он уже ссылается. [ Example:
struct S { typedef struct A { } A; // OK typedef struct B B; // OK typedef A A; // error };
— end example ]
Если typedef спецификатор используется для переопределения в данной области сущности, на которую можно ссылаться с помощью elaborated-type-specifier, на эту сущность можно продолжать ссылаться с помощью elaborated-type-specifierили как перечисление или имя класса в перечислении или определении класса соответственно. [ Example:
struct S; typedef struct S S; int main() { struct S* p; // OK } struct S { }; // OK
— end example ]
В данной области typedef спецификатор не должен использоваться для переопределения имени любого типа, объявленного в этой области, для ссылки на другой тип. [ Example:
class complex { /* ... */ }; typedef int complex; // error: redefinition
— end example ]
Точно так же в данной области действия класс или перечисление не должны объявляться с тем же именем, что и объект typedef-name, объявленный в этой области, и относится к типу, отличному от самого класса или перечисления. [ Example:
typedef int complex; class complex { /* ... */ }; // error: redefinition
— end example ]
[ Имя типа класса или его cv-квалифицированная версия также является ( ). Если a используется для идентификации объекта , определения, объявления или объявления, программа имеет неправильный формат . ] [ Note: typedef-nameclass-name[class.name]typedef-nameelaborated-type-specifier class constructor destructor — end note Example:
struct S { S(); ~S(); }; typedef struct S T; S a = T(); // OK struct T * p; // error
— end example ]
Если объявление typedef определяет безымянный класс (или перечисление), первый typedef-nameобъявленный в объявлении тип этого класса (или тип перечисления) используется для обозначения типа класса (или типа перечисления) только для целей связывания ([basic.link]). [ Example:
typedef struct { } *ps, S; // S is the class name for linkage purposes
— end example ]
Спецификатор constexpr должен применяться только к определению переменной или шаблона переменной или к объявлению функции или шаблона функции. Функция или статический член данных, объявленный с помощью constexpr спецификатора, неявно является встроенной функцией или переменной ([dcl.inline]). Если какое-либо объявление функции или шаблона функции имеет constexpr спецификатор, то все его объявления должны содержать constexpr спецификатор. [ Note: Явная специализация может отличаться от объявления шаблона в отношении constexpr спецификатора. ] [ Параметры функции не могут быть объявлены . ] [ — end note Note: constexpr — end note Example:
constexpr void square(int &x); // OK: declaration constexpr int bufsz = 1024; // OK: definition constexpr struct pixel { // error: pixel is a type int x; int y; constexpr pixel(int); // OK: declaration }; constexpr pixel::pixel(int a) : x(a), y(x) // OK: definition { square(x); } constexpr pixel small(2); // error: square not defined, so small(2) // not constant ([expr.const]) so constexpr not satisfied constexpr void square(int &x) { // OK: definition x *= x; } constexpr pixel large(4); // OK: square defined int next(constexpr int x) { // error: not for parameters return x + 1; } extern constexpr int memsz; // error: not a definition
— end example ]
constexpr Спецификатор используется в объявлении функции, не конструктор заявляет , что функция будет constexpr function. Точно так же constexpr спецификатор, используемый в объявлении конструктора, объявляет, что этот конструктор является constexpr constructor.
Определение функции constexpr должно удовлетворять следующим требованиям:
не должно быть virtual;
его возвращаемый тип должен быть буквальным;
каждый из его типов параметров должен быть буквальным типом;
это function-bodyдолжно быть = delete, = defaultили, compound-statement которое не содержит
ан asm-definition,
goto заявление,
ан identifier label,
а try-block, или
определение переменной нелитерального типа, либо статической, либо продолжительности хранения потока, либо для которой не выполняется инициализация.
[ Example:
constexpr int square(int x) { return x * x; } // OK constexpr long long_max() { return 2147483647; } // OK constexpr int abs(int x) { if (x < 0) x = -x; return x; // OK } constexpr int first(int n) { static int value = n; // error: variable has static storage duration return value; } constexpr int uninit() { int a; // error: variable is uninitialized return a; } constexpr int prev(int x) { return --x; } // OK constexpr int g(int x, int n) { // OK int r = 1; while (--n > 0) r *= x; return r; }
— end example ]
Определение конструктора constexpr должно удовлетворять следующим требованиям:
у класса не должно быть никаких виртуальных базовых классов;
каждый из типов параметров должен быть буквальным типом;
его function-bodyне должно быть function-try-block.
Кроме того, он либо function-bodyдолжен быть = delete, либо удовлетворять следующим требованиям:
либо его function-bodyдолжно быть = default, либо compound-statementего function-body должно удовлетворять требованиям для function-bodyфункции constexpr;
каждый невариантный нестатический член данных и подобъект базового класса должен быть инициализирован ([class.base.init]);
если класс является объединением, имеющим вариантные члены ([class.union]), должен быть инициализирован ровно один из них;
если класс является подобным объединению классом, но не является объединением, для каждого из его анонимных членов объединения, имеющих вариантные члены, должен быть инициализирован ровно один из них;
для конструктора без делегирования каждый конструктор, выбранный для инициализации нестатических членов данных и подобъектов базового класса, должен быть конструктором constexpr;
для делегирующего конструктора целевой конструктор должен быть конструктором constexpr.
[ Example:
struct Length { constexpr explicit Length(int i = 0) : val(i) { } private: int val; };
— end example ]
Для функции constexpr или конструктора constexpr, который не является ни заданным по умолчанию, ни шаблоном, если не существует таких значений аргументов, что вызов функции или конструктора мог быть вычисленным подвыражением a core constant expression, или, для конструктора, константным инициализатором для некоторого объекта ([basic.start.static]), программа некорректна, диагностика не требуется. [ Example:
constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() { return f(true); } // ill-formed, no diagnostic required struct B { constexpr B(int x) : i(0) { } // x is unused int i; }; int global; struct D : B { constexpr D() : B(global) { } // ill-formed, no diagnostic required // lvalue-to-rvalue conversion on non-constant global };
— end example ]
Если конкретизированная специализация шаблона шаблона функции constexpr или функции-члена шаблона класса не сможет удовлетворить требования для функции constexpr или конструктора constexpr, эта специализация по-прежнему является функцией constexpr или конструктором constexpr, даже если вызов такой функции не может присутствовать в постоянном выражении. Если никакая специализация шаблона не удовлетворяет требованиям для функции constexpr или конструктора constexpr, если рассматривать его как функцию или конструктор, не являющиеся шаблоном, шаблон сформирован неправильно, диагностика не требуется.
Вызов функции constexpr дает тот же результат, что и вызов эквивалентной функции non-constexpr во всех отношениях, за исключением того, что
вызов функции constexpr может появиться в constant expression и
copy elision является обязательным в постоянном выражении ([class.copy]).
Спецификатор constexpr не влияет на тип функции constexpr или конструктора constexpr. [ Example:
constexpr int bar(int x, int y) // OK { return x + y + x*y; } // ... int bar(int x, int y) // error: redefinition of bar { return x * 2 + 3 * y; }
— end example ]
constexpr Спецификатор используется в объявлении объекта объявляет объект как const. Такой объект должен иметь буквальный тип и быть инициализирован. В любом constexpr объявлении переменной полное выражение инициализации должно быть constant expression. [ Example:
struct pixel { int x, y; }; constexpr pixel ur = { 1294, 1024 }; // OK constexpr pixel origin; // error: initializer missing
— end example ]
Объявление функции ([dcl.fct], [class.mfct], [class.friend]) с inline спецификатором объявляет какойinline function. Встроенный спецификатор указывает реализации, что встроенная подстановка тела функции в точке вызова предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой встроенной замены в точке вызова; однако, даже если эта встроенная подстановка опущена, все равно должны соблюдаться другие правила для встроенных функций, указанные в этом разделе.
Спецификатор inline не должен появляться в объявлении области действия блока.94 Если inline спецификатор используется в объявлении дружественной функции, это объявление должно быть определением, или функция должна быть ранее объявлена встроенной.
Встроенная функция или переменная должна быть определена в каждой единице перевода, в которой она используется odr, и должна иметь точно такое же определение во всех случаях ([basic.def.odr]). [ Note: Вызов встроенной функции или использование встроенной переменной может встречаться до того, как ее определение появится в блоке перевода. ] Если определение функции или переменной появляется в единице перевода перед ее первым объявлением как встроенное, программа имеет неправильный формат. Если функция или переменная с внешней связью объявляется встроенной в одной единице перевода, она должна быть объявлена встроенной во всех единицах перевода, в которых она появляется; Диагностика не требуется. Встроенная функция или переменная с внешней связью должна иметь один и тот же адрес во всех единицах трансляции. [ Локальная переменная в инлайн функции с внешним связыванием всегда относится к тому же объекту. Тип, определенный в теле встроенной функции с внешней связью, является одним и тем же типом во всех единицах перевода. ] — end note Note: static — end note
inline Ключевое слово не имеет никакого влияния на связь функции.
Спецификаторы типа:
type-specifier: simple-type-specifier elaborated-type-specifier typename-specifier cv-qualifier
type-specifier-seq: type-specifier attribute-specifier-seqopt type-specifier type-specifier-seq
defining-type-specifier: type-specifier class-specifier enum-specifier
defining-type-specifier-seq: defining-type-specifier attribute-specifier-seqopt defining-type-specifier defining-type-specifier-seq
Необязательное attribute-specifier-seqв a type-specifier-seq или a defining-type-specifier-seq относится к типу, обозначенному предыдущим символом type-specifiers или defining-type-specifiers ([dcl.meaning]). Параметр attribute-specifier-seqвлияет на тип только для объявления, в котором он появляется, а не для других объявлений, связанных с тем же типом.
Как правило, defining-type-specifier разрешается не более одного в полном decl-specifier-seqa declarationили в a defining-type-specifier-seq, и не более одного type-specifier разрешается в a type-specifier-seq. Единственными исключениями из этого правила являются следующие:
const может сочетаться с любым спецификатором типа, кроме самого себя.
volatile может сочетаться с любым спецификатором типа, кроме самого себя.
signed или unsigned могут быть объединены с char, long, shortили int.
short или long можно комбинировать с int.
long можно комбинировать с double.
long можно комбинировать с long.
За исключением объявления конструктора, деструктора или функции преобразования, по крайней мере один, defining-type-specifierкоторый не является, cv-qualifierдолжен появиться в завершенном type-specifier-seqили завершенном виде decl-specifier-seq.95
[ , И обсуждаются в , п , и , соответственно. Остальные обсуждаются в оставшейся части этого раздела. ] Note: enum-specifiersclass-specifierstypename-specifiers[dcl.enum] [class][temp.res]type-specifiers — end note
Не существует специального положения для a, в decl-specifier-seqкотором отсутствует a type-specifierили для a, type-specifierкоторый только указывает cv-qualifiers. Правило C «неявных int» больше не поддерживается.
Их два cv-qualifiers, const и volatile. Каждый cv-qualifierдолжен появляться не более одного раза в cv-qualifier-seq. Если a cv-qualifierпоявляется в a decl-specifier-seq, то init-declarator-list или member-declarator-listдекларации не может быть пустым. [ Note: [basic.type.qualifier] и [dcl.fct] опишите, как cv-квалификаторы влияют на типы объектов и функций. ] Избыточные CV-квалификации игнорируются. [ Например, они могут быть введены с помощью typedefs. ] — end note Note: — end note
[ Note: Объявление переменной const может повлиять на ее linkage ([dcl.stc]) и удобство использования в constant expressions. Как описано в [dcl.init], определение объекта или подобъекта типа с указанием const должно указывать инициализатор или подвергаться инициализации по умолчанию. ] — end note
Указатель или ссылка на cv-квалифицированный тип не обязательно должны указывать или ссылаться на cv-квалифицированный объект, но он обрабатывается так, как если бы он это делал; Путь доступа с указанием констант не может использоваться для изменения объекта, даже если указанный объект не является константным объектом и может быть изменен с помощью другого пути доступа. [ Note: Cv-квалификаторы поддерживаются системой типов, так что без них их нельзя ниспровергнуть casting. ] — end note
За исключением того, что любой объявленный член класса mutable может быть изменен, любая попытка изменить const объект во время его выполнения lifetime приводит к неопределенному поведению. [ Example:
const int ci = 3; // cv-qualified (initialized as required) ci = 4; // ill-formed: attempt to modify const int i = 2; // not cv-qualified const int* cip; // pointer to const int cip = &i; // OK: cv-qualified access path to unqualified *cip = 4; // ill-formed: attempt to modify through ptr to const int* ip; ip = const_cast<int*>(cip); // cast needed to convert const int* to int* *ip = 4; // defined: *ip points to i, a non-const object const int* ciq = new const int (3); // initialized as required int* iq = const_cast<int*>(ciq); // cast required *iq = 4; // undefined: modifies a const object
Другой пример:
struct X { mutable int i; int j; }; struct Y { X x; Y(); }; const Y y; y.x.i++; // well-formed: mutable member can be modified y.x.j++; // ill-formed: const-qualified member modified Y* p = const_cast<Y*>(&y); // cast away const-ness of y p->x.i = 99; // well-formed: mutable member can be modified p->x.j = 99; // undefined: modifies a const member
— end example ]
Семантика доступа через изменчивое значение glvalue определяется реализацией. Если предпринята попытка доступа к объекту, определенному с типом с изменяемым атрибутом, с помощью энергонезависимого значения glvalue, поведение не определено.
[ Note: volatile является подсказкой реализации, чтобы избежать агрессивной оптимизации, связанной с объектом, потому что значение объекта может быть изменено средствами, не обнаруживаемыми реализацией. Кроме того, для некоторых реализаций volatile может указывать на то, что для доступа к объекту требуются специальные аппаратные инструкции. См. [intro.execution] Подробную семантику. В общем, семантика volatile в C ++ должна быть такой же, как и в C. ] — end note
Спецификаторы простого типа:
simple-type-specifier: nested-name-specifieropt type-name nested-name-specifier template simple-template-id nested-name-specifieropt template-name char char16_t char32_t wchar_t bool short int long signed unsigned float double void auto decltype-specifier
type-name: class-name enum-name typedef-name simple-template-id
decltype-specifier: decltype ( expression ) decltype ( auto )
Это simple-type-specifier auto заполнитель для выводимого типа ([dcl.spec.auto]). A type-specifierформы является заполнителем для выведенного типа класса ( ). Он должен называть шаблон класса, который не является внедренным именем класса. Другой указывает либо ранее объявленный тип, тип, определяемый выражением, либо один из . В таблице приведены допустимые комбинации и указанные ими типы.typenameopt nested-name-specifieropt template-name[dcl.type.class.deduct]template-namesimple-type-specifiersfundamental types 11simple-type-specifiers
Спецификатор (ы) | Тип |
type-name | названный тип |
simple-template-id | тип, как определено в [temp.names] |
template-name | заполнитель для выводимого типа |
символ | «Чар» |
беззнаковый символ | «Беззнаковый символ» |
подписанный символ | «Подписанный символ» |
char16_t | «Char16_t» |
char32_t | «Char32_t» |
bool | «Булево» |
беззнаковый | «Беззнаковое целое» |
беззнаковое целое | «Беззнаковое целое» |
подписано | «Int» |
подписанный int | «Int» |
int | «Int» |
беззнаковый короткий int | «Короткое беззнаковое целое» |
беззнаковый короткий | «Короткое беззнаковое целое» |
беззнаковое длинное целое | «Беззнаковое длинное целое число» |
беззнаковый длинный | «Беззнаковое длинное целое число» |
беззнаковый длинный длинный int | «Беззнаковый длинный длинный int» |
беззнаковый длинный длинный | «Беззнаковый длинный длинный int» |
подписанный длинный int | «Длинный интервал» |
подписан долго | «Длинный интервал» |
подписанный длинный длинный int | «Длинный длинный интервал» |
подписан долго | «Длинный длинный интервал» |
длинный длинный int | «Длинный длинный интервал» |
долго долго | «Длинный длинный интервал» |
длинный интервал | «Длинный интервал» |
длинный | «Длинный интервал» |
подписанный короткий int | «Короткий интервал» |
подписанный короткий | «Короткий интервал» |
короткий int | «Короткий интервал» |
короткая | «Короткий интервал» |
wchar_t | «Wchar_t» |
плавать | "плавать" |
двойной | "двойной" |
длинный двойной | «Длинный дубль» |
пустота | "пустота" |
авто | заполнитель для выводимого типа |
decltype (авто) | заполнитель для выводимого типа |
decltype (expression) | тип, как определено ниже |
Когда simple-type-specifiers разрешено несколько , они могут свободно смешиваться с другими decl-specifiers в любом порядке. [ Note: Это определяется реализацией, представлены ли объекты char типа в виде количества со знаком или без знака. Спецификатор signed заставляет char объекты подписываться; в других контекстах это избыточно. ] — end note
Для выражения eтип, обозначенный decltype(e) как, определяется следующим образом:
if e - это имя без скобок id-expression для структурированной привязки ([dcl.struct.bind]), decltype(e) - это ссылочный тип, как указано в спецификации объявления структурированной привязки;
в противном случае, если e без скобок id-expressionили без скобок class member access, decltype(e) это тип сущности, названной по e. Если такой сущности нет или если она e называет набор перегруженных функций, программа плохо сформирована;
в противном случае, если e - значение x, decltype(e) это T&&, где T - тип e;
в противном случае, если e - lvalue, decltype(e) is T&, где T - тип e;
в противном случае decltype(e) - это тип e.
Операнд decltype спецификатора - это unevaluated operand.
[ Example:
const int&& foo(); int i; struct A { double x; }; const A* a = new A(); decltype(foo()) x1 = 17; // type is const int&& decltype(i) x2; // type is int decltype(a->x) x3; // type is double decltype((a->x)) x4 = x3; // type is const double&
— end example ] [ Note: Правила определения задействованных типов decltype(auto) указаны в [dcl.spec.auto]. ] — end note
Если операндом a decltype-specifierявляется prvalue, то temporary materialization conversion не применяется и объект результата для prvalue не предоставляется. Тип prvalue может быть неполным. [ Note: В результате для prvalue память не выделяется и не уничтожается. Таким образом, тип класса не создается в результате того, что он является типом вызова функции в этом контексте. В этом контексте обычная цель написания выражения - просто сослаться на его тип. В этом смысле a decltype-specifierаналогичен использованию a typedef-name, поэтому обычные причины для требования полного типа не применяются. В частности, нет необходимости выделять память для временного объекта или принудительно применять семантические ограничения, связанные с вызовом деструктора типа. ] [ В отличие от предыдущего правила, круглые скобки не имеют особого значения в этом контексте. ] [ — end note Note: — end note Example:
template<class T> struct A { ~A() = delete; }; template<class T> auto h() -> A<T>; template<class T> auto i(T) // identity -> T; template<class T> auto f(T) // #1 -> decltype(i(h<T>())); // forces completion of A<T> and implicitly uses A<T>::~A() // for the temporary introduced by the use of h(). // (A temporary is not introduced as a result of the use of i().) template<class T> auto f(T) // #2 -> void; auto g() -> void { f(42); // OK: calls #2. (#1 is not a viable candidate: type deduction // fails ([temp.deduct]) because A<int>::~A() is implicitly used in its // decltype-specifier) } template<class T> auto q(T) -> decltype((h<T>())); // does not force completion of A<T>; A<T>::~A() is not implicitly // used within the context of this decltype-specifier void r() { q(42); // Error: deduction against q succeeds, so overload resolution selects // the specialization “q(T) -> decltype((h<T>())) [with T=int]”. // The return type is A<int>, so a temporary is introduced and its // destructor is used, so the program is ill-formed. }
— end example ]
elaborated-type-specifier: class-key attribute-specifier-seqopt nested-name-specifieropt identifier class-key simple-template-id class-key nested-name-specifier templateopt simple-template-id enum nested-name-specifieropt identifier
attribute-specifier-seqНе должно появляться в elaborated-type-specifier тех пор , пока последний не является единственным компонентом декларации. Если an elaborated-type-specifierявляется единственной составляющей декларации, декларация неправильно сформирована, если только она не является explicit specialization, an explicit instantiation или имеет одну из следующих форм:
class-key attribute-specifier-seqopt identifier ; friend class-key ::opt identifier ; friend class-key ::opt simple-template-id ; friend class-key nested-name-specifier identifier ; friend class-key nested-name-specifier templateopt simple-template-id ;
В первом случае, attribute-specifier-seqесли есть, принадлежит объявленному классу; атрибуты в attribute-specifier-seqвпоследствии считаются атрибутами класса, когда бы он ни был назван.
[basic.lookup.elab] описывает, как выполняется поиск имени identifierв файле elaborated-type-specifier. Если identifierразрешается в class-nameили enum-name, elaborated-type-specifier вводит его в объявление так же, как simple-type-specifierвводит свой type-name. Если identifierразрешается в typedef-nameили simple-template-idрешает шаблон псевдонима специализации, то elaborated-type-specifierплохо сформирован. [ Note: Это означает, что в шаблоне класса с шаблоном type-parameter Tобъявление
friend class T;
плохо сформирован. Однако подобное объявление friend T; разрешено ([class.friend]). ] — end note
class-keyИли enum ключевое слово присутствует в elaborated-type-specifierсогласовывают в натуральной форме с декларацией , в которой имя в elaborated-type-specifierссылается. Это правило также применяется к форме elaborated-type-specifierобъявления класса class-nameили, friend поскольку оно может быть истолковано как относящееся к определению класса. Таким образом, в любом elaborated-type-specifier, то enum ключевое слово должно использоваться для обозначения enumeration, то должны быть использованы для обозначения , и либо или должны быть использованы для обозначения объявляются с помощью или . [ union class-key union class struct class-key class class struct class-key Example:
enum class E { a, b };
enum E x = E::a; // OK
— end example ]
Символы auto и используются для обозначения типа заполнителя, который позже будет заменен путем вычитания из инициализатора. Также используется , чтобы ввести тип функции , имеющий или чтобы показать , что лямбда является общим лямбда ( ). Также используется для введения . decltype(auto) type-specifiers auto type-specifiertrailing-return-type[expr.prim.lambda.closure] auto type-specifierstructured binding declaration
Тип заполнитель может появиться с функцией описателем в decl-specifier-seq, type-specifier-seq, conversion-function-id, или trailing-return-type, в любом контексте , где такой описатель является действительным. Если декларатор функции включает в себя trailing-return-type([dcl.fct]), который trailing-return-typeуказывает объявленный тип возвращаемого значения функции. В противном случае декларатор функции должен объявить функцию. Если объявленный тип возвращаемого значения функции содержит тип-заполнитель, тип возвращаемого значения функции выводится из неотброшенных return операторов, если таковые имеются, в теле функции ([stmt.if]).
Если появляется как один из в a из a , лямбда - это ( ). [ auto type-specifierdecl-specifiers decl-specifier-seqparameter-declarationlambda-expression generic lambda [expr.prim.lambda.closure] Example:
auto glambda = [](int i, auto a) { return i; }; // OK: a generic lambda
— end example ]
Тип переменной, объявленной с использованием auto или decltype(auto) , выводится из ее инициализатора. Это использование разрешено в инициализирующем объявлении ([dcl.init]) переменной. auto или decltype(auto) должно отображаться как одно из decl-specifiers в, decl-specifier-seqа за ним decl-specifier-seq должно следовать одно или несколько declarators, за каждым из которых должно следовать непустое значение initializer. В initializerформе
( expression-list )
это expression-listдолжно быть единым assignment-expression. [ Example:
auto x = 5; // OK: x has type int const auto *v = &x, u = 6; // OK: v has type const int*, u has type const int static auto y = 0.0; // OK: y has type double auto int r; // error: auto is not a storage-class-specifier auto f() -> int; // OK: f returns int auto g() { return 0.0; } // OK: g returns double auto h(); // OK: h's return type will be deduced when it is defined
— end example ]
Тип заполнитель также может быть использован в type-specifier-seqв new-type-idили type-idв А new-expression и , как decl-specifier из parameter-declaration-е decl-specifier-seq в template-parameter.
Программа, которая использует auto или decltype(auto) в контексте, явно не разрешенном в этом разделе, имеет неправильный формат.
Если init-declarator-listсодержит более одного init-declarator, все они должны формировать объявления переменных. Тип каждой объявленной переменной определяется placeholder type deduction, и если тип, заменяющий тип заполнителя, не является одинаковым при каждом выводе, программа имеет неправильный формат.
[ Example:
auto x = 5, *y = &x; // OK: auto is int auto a = 5, b = { 1, 2 }; // error: different types for auto
— end example ]
Если функция с объявленным типом возвращаемого значения, содержащим тип заполнителя, имеет несколько неотброшенных return операторов, тип возвращаемого значения выводится для каждого такого return оператора. Если выведенный тип не является одинаковым в каждом из выводов, программа сформирована неправильно.
Если функция с объявленным типом возврата, которая использует тип заполнителя, не имеет неотброшенных return операторов, тип возвращаемого значения выводится, как если бы из return оператора без операнда в закрывающей фигурной скобке тела функции. [ Example:
auto f() { } // OK, return type is void auto* g() { } // error, cannot deduce auto* from void()
— end example ]
Если для определения типа выражения требуется тип сущности с невыявленным типом заполнителя, программа имеет неправильный формат. Однако после того, как неотброшенный return оператор был замечен в функции, тип возвращаемого значения, выведенный из этого оператора, можно использовать в остальной части функции, в том числе в других return операторах. [ Example:
auto n = n; // error, n's type is unknown auto f(); void g() { &f; } // error, f's return type is unknown auto sum(int i) { if (i == 1) return i; // sum's return type is int else return sum(i-1)+i; // OK, sum's return type has been deduced }
— end example ]
Вывод типа возвращаемого значения для шаблона функции с заполнителем в объявленном типе происходит при создании экземпляра определения, даже если тело функции содержит return оператор с операндом, не зависящим от типа. [ Note: Следовательно, любое использование специализации шаблона функции вызовет неявное создание экземпляра. Любые ошибки, возникающие из-за этого экземпляра, не относятся к непосредственному контексту типа функции и могут привести к неправильному формату программы ([temp.deduct]). ] [ — end note Example:
template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // instantiates f<int> to deduce return type template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // instantiates both fs to determine return types, // chooses second
— end example ]
Повторные объявления или специализации функции или шаблона функции с объявленным типом возврата, который использует тип заполнителя, также должны использовать этот заполнитель, а не выводимый тип. [ Example:
auto f(); auto f() { return 42; } // return type is int auto f(); // OK int f(); // error, cannot be overloaded with auto f() decltype(auto) f(); // error, auto and decltype(auto) don't match template <typename T> auto g(T t) { return t; } // #1 template auto g(int); // OK, return type is int template char g(char); // error, no matching template template<> auto g(double); // OK, forward declaration with unknown return type template <class T> T g(T t) { return t; } // OK, not functionally equivalent to #1 template char g(char); // OK, now there is a matching template template auto g(float); // still matches #1 void h() { return g(42); } // error, ambiguous template <typename T> struct A { friend T frf(T); }; auto frf(int i) { return i; } // not a friend of A<int>
— end example ]
An explicit instantiation declaration не вызывает создание экземпляра сущности, объявленной с использованием типа заполнителя, но также не предотвращает создание экземпляра этой сущности по мере необходимости для определения ее типа. [ Example:
template <typename T> auto f(T t) { return t; } extern template auto f(int); // does not instantiate f<int> int (*p)(int) = f; // instantiates f<int> to determine its return type, but an explicit // instantiation definition is still required somewhere in the program
— end example ]
Placeholder type deduction - это процесс, с помощью которого тип, содержащий тип-заполнитель, заменяется выведенным типом.
Тип, T содержащий тип заполнителя и соответствующий инициализатор e, определяются следующим образом:
для неотброшенного return оператора, который встречается в функции, объявленной с возвращаемым типом, который содержит тип-заполнитель, T является объявленным возвращаемым типом и e является операндом return оператора. Если у return оператора нет операнда, то e есть void();
для переменной, объявленной с типом, содержащим тип-заполнитель, T является объявленным типом переменной и e инициализатором. Если инициализация - это инициализация с прямым списком, инициализатор должен braced-init-list содержать только один элемент assignment-expression и e является assignment-expression;
для параметра шаблона, не являющегося типом, объявленного с типом, содержащим тип-заполнитель, T является объявленным типом параметра шаблона, не являющимся типом, и e является соответствующим аргументом шаблона.
В случае return заявления, без операнда или с операндом типа void, T должны быть либо decltype(auto) или cv auto.
Если вывод предназначен для return оператора и e представляет собой braced-init-list([dcl.init.list]), программа сформирована неправильно.
Если заполнитель - это , замена выведенного типа определяется с использованием правил вывода аргументов шаблона. Получить от путем замены вхождений либо на новый параметр шаблона изобретенного типа, либо, если инициализация - инициализация списка-копирования, на . Выведите значение для использования правил , где - тип параметра шаблона функции, а соответствующий аргумент - . Если вычет не удается, декларация имеет неверный формат. В противном случае получается заменой выведенного на . [ auto type-specifierT' T P T auto U std::initializer_list<U> U template argument deduction from a function call P e T' U P Example:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int> auto x2 = { 1, 2.0 }; // error: cannot deduce element type auto x3{ 1, 2 }; // error: not a single element auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int> auto x5{ 3 }; // decltype(x5) is int
— end example ]
[ Example:
const auto &i = expr;
Тип i - это выведенный тип параметра u при вызове f(expr) следующего шаблона придуманной функции:
template <class U> void f(const U& u);
— end example ]
Если заполнителем является , должен быть только заполнитель. Тип, выведенный для , определяется, как описано в , как если бы он был операндом . [ decltype(auto) type-specifierT T [dcl.type.simple]e decltype Example:
int i; int&& f(); auto x2a(i); // decltype(x2a) is int decltype(auto) x2d(i); // decltype(x2d) is int auto x3a = i; // decltype(x3a) is int decltype(auto) x3d = i; // decltype(x3d) is int auto x4a = (i); // decltype(x4a) is int decltype(auto) x4d = (i); // decltype(x4d) is int& auto x5a = f(); // decltype(x5a) is int decltype(auto) x5d = f(); // decltype(x5d) is int&& auto x6a = { 1, 2 }; // decltype(x6a) is std::initializer_list<int> decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression auto *x7a = &i; // decltype(x7a) is int* decltype(auto)*x7d = &i; // error, declared type is not plain decltype(auto)
— end example ]
Если заполнитель для выведенного типа класса отображается как a decl-specifier в decl-specifier-seq объявлении инициализации ([dcl.init]) переменной, заполнитель заменяется типом возвращаемого значения функции, выбранной разрешением перегрузки для вывода шаблона класса ([over.match.class.deduct]). Если за decl-specifier-seq символом следует init-declarator-list или, member-declarator-list содержащий более одного declarator, тип, который заменяет заполнитель, должен быть одинаковым при каждом вычитании.
Заполнитель для выведенного типа класса также может использоваться type-specifier-seq в new-type-idили type-id в new-expression, или как simple-type-specifier в явном преобразовании типа (функциональная нотация) ([expr.type.conv]). Заполнитель для выведенного типа класса не должен появляться ни в каком другом контексте.
[ Example:
template<class T> struct container { container(T t) {} template<class Iter> container(Iter beg, Iter end); }; template<class Iter> container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>; std::vector<double> v = { /* ... */ }; container c(7); // OK, deduces int for T auto d = container(v.begin(), v.end()); // OK, deduces double for T container e{5, 6}; // error, int is not an iterator
— end example ]