10 Declarations [dcl.dcl]

10.1 Specifiers [dcl.spec]

Спецификаторы, которые можно использовать в объявлении:

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

10.1.1 Storage class specifiers [dcl.stc]

Спецификаторы класса хранения:

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-specifierend 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]).

10.1.2 Function specifiers [dcl.fct.spec]

Function-specifiers может использоваться только в объявлениях функций.

function-specifier:
	virtual
	explicit

Спецификатор virtual должен использоваться только в начальном объявлении нестатической функции-члена класса; см [class.virtual].

Спецификатор explicit должен использоваться только в объявлении конструктора или функции преобразования в определении его класса; видеть [class.conv.ctor] и [class.conv.fct].

10.1.3 The typedef specifier [dcl.typedef]

Объявления, содержащие 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 noteExample:

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]

10.1.4 The friend specifier [dcl.friend]

Спецификатор friend используется для указания доступа к членам класса; см [class.friend].

10.1.5 The constexpr specifier [dcl.constexpr]

Спецификатор constexpr должен применяться только к определению переменной или шаблона переменной или к объявлению функции или шаблона функции. Функция или статический член данных, объявленный с помощью constexpr спецификатора, неявно является встроенной функцией или переменной ([dcl.inline]). Если какое-либо объявление функции или шаблона функции имеет constexpr спецификатор, то все его объявления должны содержать constexpr спецификатор. [ Note: Явная специализация может отличаться от объявления шаблона в отношении constexpr спецификатора. ] [ Параметры функции не могут быть объявлены . ] [ end noteNote: constexprend noteExample:

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]

10.1.6 The inline specifier [dcl.inline]

Спецификатор inline может применяться только к объявлению или определению переменной или функции.

Объявление функции ([dcl.fct], [class.mfct], [class.friend]) с inline спецификатором объявляет какойinline function. Встроенный спецификатор указывает реализации, что встроенная подстановка тела функции в точке вызова предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой встроенной замены в точке вызова; однако, даже если эта встроенная подстановка опущена, все равно должны соблюдаться другие правила для встроенных функций, указанные в этом разделе.

Объявление переменной со inline спецификатором объявляет inline variable.

Функция, определенная в определении класса, является встроенной функцией.

Спецификатор inline не должен появляться в объявлении области действия блока.94 Если inline спецификатор используется в объявлении дружественной функции, это объявление должно быть определением, или функция должна быть ранее объявлена ​​встроенной.

Встроенная функция или переменная должна быть определена в каждой единице перевода, в которой она используется odr, и должна иметь точно такое же определение во всех случаях ([basic.def.odr]). [ Note: Вызов встроенной функции или использование встроенной переменной может встречаться до того, как ее определение появится в блоке перевода. ] Если определение функции или переменной появляется в единице перевода перед ее первым объявлением как встроенное, программа имеет неправильный формат. Если функция или переменная с внешней связью объявляется встроенной в одной единице перевода, она должна быть объявлена ​​встроенной во всех единицах перевода, в которых она появляется; Диагностика не требуется. Встроенная функция или переменная с внешней связью должна иметь один и тот же адрес во всех единицах трансляции. [ Локальная переменная в инлайн функции с внешним связыванием всегда относится к тому же объекту. Тип, определенный в теле встроенной функции с внешней связью, является одним и тем же типом во всех единицах перевода. ]end noteNote: static end note

inline Ключевое слово не имеет никакого влияния на связь функции.

10.1.7 Type specifiers [dcl.type]

Спецификаторы типа:

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» больше не поддерживается.

10.1.7.1 The cv-qualifiers [dcl.type.cv]

Их два 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 noteNote: 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

10.1.7.2 Simple type specifiers [dcl.type.simple]

Спецификаторы простого типа:

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

Таблица 11 - simple-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 noteNote: end noteExample:

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]

10.1.7.3 Elaborated type specifiers [dcl.type.elab]

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-keyExample:

enum class E { a, b };
enum E x = E::a;                // OK

end example]

10.1.7.4 The auto specifier [dcl.spec.auto]

Символы 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-declarationdecl-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 noteExample:

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]

Функция, объявленная с возвращаемым типом, использующим тип-заполнитель, не должна быть virtual.

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]

10.1.7.4.1 Placeholder type deduction [dcl.type.auto.deduct]

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 PExample:

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 decltypeExample:

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]

10.1.7.5 Deduced class template specialization types [dcl.type.class.deduct]

Если заполнитель для выведенного типа класса отображается как 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]