Decltype c что это
Перейти к содержимому

Decltype c что это

  • автор:

2.5.3. Спецификатор типа decltype

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

decltype(f()) sum = x; // sum имеет тот тип,

// который возвращает функция f

Здесь компилятор не вызывает функцию f(), но он использует тип, который возвратил бы такой вызов для переменной sum. Таким образом, компилятор назначает переменной sum тот же тип, который был бы возвращен при вызове функции f().

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

const int ci = 0, &cj = ci;

decltype(ci) x = 0; // x имеет тип const int

decltype(cj) y = x; // y имеет тип const int& и связана с x

decltype(сj) z; // ошибка: z — ссылка, она должна быть инициализирована

Поскольку cj — ссылка, decltype (cj) — ссылочный тип. Как и любую другую ссылку, ссылку z следует инициализировать.

Следует заметить, что спецификатор decltype — единственный контекст, в котором переменная определена, поскольку ссылка не рассматривается как синоним объекта, на который она ссылается.

Спецификатор decltype и ссылки

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

// decltype выражение может быть ссылочным типом

decltype(r + 0) b; // ok: сложение возвращает тип int; b имеет тип int

decltype(*p) с; // ошибка: с имеет тип int& и требует инициализации

Здесь r — ссылка, поэтому decltype(r) возвращает ссылочный тип. Если необходим тип, на который ссылается ссылка r, можно использовать ее в таком выражении, как r + 0, поскольку оно возвращает значение не ссылочного типа.

С другой стороны, оператор обращения к значению — пример выражения, для которого спецификатор decltype возвращает ссылку. Как уже упоминалось, при обращении к значению указателя возвращается объект, на который он указывает. Кроме того, этому объекту можно присвоить значение. Таким образом, decltype(*p) выведет тип int&, а не просто int.

Еще одно важное различие между спецификаторами decltype и auto в том, что выведение, осуществляемое спецификатором decltype, зависит от формы данного выражения. Не всегда понимают то, что включение имени переменной в круглые скобки влияет на тип, возвращаемый спецификатором decltype. При применении спецификатора decltype к переменной без круглых скобок получается тип этой переменной. Если заключить имя переменной в одни или несколько круглых скобок, то компилятор будет рассматривать операнд как выражение. Переменная — это выражение, которое способно быть левым операндом присвоения. В результате спецификатор decltype для такого выражения возвратит ссылку.

// decltype переменной в скобках — всегда ссылка

decltype((i)) d; // ошибка: d — int& и должна инициализироваться

decltype(i) e; // ok: e имеет тип int (не инициализирована)

Помните, что спецификатор decltype((переменная)) (обратите внимание на парные круглые скобки) всегда возвращает ссылочный тип, а спецификатор decltype(переменная) возвращает ссылочный тип, только если переменная является ссылкой.

Упражнения раздела 2.5.3

Упражнение 2.36. Определите в следующем коде тип каждой переменной и значения, которые будет иметь каждая из них по завершении.

Упражнение 2.37. Присвоение — это пример выражения, которое возвращает ссылочный тип. Тип — это ссылка на тип левого операнда. Таким образом, если переменная i имеет тип int, то выражение i = x имеет тип int&. С учетом этого определите тип и значение каждой переменной в следующем коде:

decltype(а = b) d = а;

Упражнение 2.38. Опишите различия выведения типа спецификаторами decltype и auto. Приведите пример выражения, где спецификаторы auto и decltype выведут тот же тип, и пример, где они выведут разные типы.

Секреты auto и decltype

Новый стандарт языка принят относительно давно и сейчас уже, наверное, нет программиста, который не слышал о новых ключевых словах auto и decltype. Но как почти с любым аспектом С++, использование этих новых инструментов не обходится без нюансов. Некоторые из них я постараюсь осветить в этой статье.

Для разминки предлагаю начать с небольшого теста.

Тест

1. Какой тип будет у переменных ri1..riN после выполнения следующего кода?

int foo(); int& foo1(); const int foo2(); const int& foo3(); int main()

Скомпилируются ли следующие фрагменты?

2. auto lmbd = [](auto i); 3. void foo(auto i); 4. decltype(auto) var = some_expression; //WTF?! 5. auto var = ; //Если да, какой тип будет у var? 6. template void foo(T t)<> foo(); 
Теория

К механизму вывода типов, используемому в шаблонах в С++11 добавилось два новых механизма: auto и decltype. И чтобы жизнь программистам не казалась медом, все эти 3 механизма выводят типы по-своему. Механизм, используемый auto, в точности копирует механизм шаблонов, за исключением типа std::initializer_list.

auto var = ; // Ok, var будет иметь тип std::initializer_list template void foo(T t); foo(); // Не компилируется 

Объяснений такому поведению немного и все они не отличаются внятностью. Скотт Мейерс, например, по этому поводу пишет так: “I have no idea why type deduction for auto and for templates is not identical. If you know, please tell me!”. В С++14 этот механизм менять не собираются. За объяснение можно попровать принять тот факт, что работают, например, такие удивительные вещи:

template void fill_from_list(T& cont, const T& l); std::vector v; fill_from_list(v, ); 
Auto

Итак, как же `auto` выводит тип? К сожалению, здесь нет простого правила на все случаи жизни, кроме, пожалуй, того, что `auto` при выводе типа в общем случае отбрасывает cv квалификаторы и ссылки. Ниже я перечислю самые важные моменты.

auto var = some_expression; 

Если тип some_expression T* или const T*, то тип var также будет T* или const T* соответственно. Пока без сюрпизов. Дальше — интереснее. Пожалуй самое важное с практической точки зрения правило заключается в том, что если тип some_expressionT, const T, T& или const T&, то типом var будет T. Это, впрочем, если задуматься, вполне логично, ведь в этом случае значение, возвращаемое some_expression копируется в var и можно смело писать вот так:

void foo(const std::list& l) < auto w = l.front(); l.pop(); // work with `w` here >
auto& var = some_expression; 

В этом случае, ожидаемо, если тип some_expressionT или const T, компилироваться это не будет, так как lvalue ссылку нельзя инициализировать rvalue. Если тип some_expressionT&, то и var будет иметь тип T&. Здесь важным моментом является то, что если тип some_expressionconst T&, то и тип var будет const T&.

auto&& var = some_expression; 

Здесь действует придуманное (или по крайней мере озвученное) Скоттом Мейерсом правило “универсальных ссылок”. Оно заключается в том, что тип var будет зависеть от того какая value category у some_expression. Если rvalue, то тип var будет T&&, если же lvalue, то T&. Cv квалификаторы при этом сохраняются.

Auto как параметр функции

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

auto foo(auto v1, auto v2) -> decltype(v1+v2) ; int foo(auto v1, bool v2); foo(“C++ is cool?”, true); 

Однако в с++14 можно будет использовать auto параметры в лямбдах.

decltype

С decltype ситуация с одной стороны сложнее (если посмотреть формальные правила), с другой стороны проще (если выделить основные моменты). Я сформулирую эти правила так, как я их понял.
Итак, следует различать два основных случая применения decltype.
1. decltype(var), когда var — это объявленная переменная (например в функции или как член класса). В этом случае decltype(var) будет иметь в точности тот тип, с которым объявлена переменная.
2. decltype(expr), expr — выражение. В этом случае типом decltype(expr) будет тип, которое могло бы вернуть это выражение, с той оговоркой, что decltype(expr) будет иметь тип T& (const T&), если expr возвращает lvalue, T, если expr возвращает rvalue типа Т (const T) и T&& (const T&&), если expr возвращает xvalue (rvalue reference).

Что значит “могло бы вернуть”? Это значит то, что decltype не вычисляет переданное ему в качестве аргумента выражение.
Несколько поясняющих примеров:

int i; decltype(i); // int decltype(i + 1); // int decltype((i)); // int& decltype(i = 4); //int& const int foo(); decltype(foo()) ;// int int&& foo1(); decltype(foo1()) ;// int&& 

В том случае, если мы не знаем lvalue нам вернет выражение, rvalue или xvalue, а тип использовать хочется, можно воспользоваться стандартным шаблоном std::remove_reference, чтобы “очистить” тип от ссылок.

Decltype(auto)

Это новая “фишка” языка, которая войдет в С++14. Она нужна для сохранения семантики decltype при объявлении auto переменных и будет использоваться в тех случаях, когда нас не будет устраивать то, что auto отбрасывает ссылки и cv квалификаторы и, возможно, в связке с новой возможностью С++14 — выводом типа возвращаемого функцией значения.

const int&& foo(); auto i = foo(); // i будет иметь тип int dectype(auto) i2 = foo(); // i2 будет иметь тип const int&& 

В последнем случае мы могли бы написать decltype(foo()), но представьте, если бы вместо foo() было выражение на 2 строчки, а такие в С++ не редкость.

Ответы

Ну и сейчас, загрузив теорию в кэш, можно попытаться ответить на вопросы теста.

int foo(); int& foo1(); const int foo2(); const int& foo3(); int main() < auto ri = foo(); // int auto ri1 = foo1(); // int auto ri2 = foo2(); // int auto ri3 = foo3(); // int auto& ri4 = foo(); // Не скомпилируется auto& ri5 = foo1(); // int& auto& ri6 = foo2(); // Не скомпилируется auto& ri7 = foo3(); // const int& auto&& ri8 = foo(); // int&& auto&& ri9 = foo1(); // int& auto&& ri10 = foo2(); // const int&& auto&& ri11 = foo3(); // const int& int k = 5; decltype(k)&& rk = k; // Не скомпилируется decltype(foo())&& ri12 = foo(); // int&& decltype(foo1())&& ri13 = foo1(); // int& int i = 3; decltype(i) ri14; // int decltype((i)) ri15; // int& >

Скомпилируются ли следующие фрагменты?

2. auto lmbd = [](auto i); // Сейчас - нет, но в С++14 - да 3. void foo(auto i); // Нет 4. decltype(auto) var = some_expression; // Да, в С++14 5. auto var = ; // Да, тип = std::initializer_list 6. template void foo(T t)<> foo(); // Нет 

decltype specifier

Inspects the declared type of an entity or the type and value category of an expression.

Contents

[edit] Syntax

decltype ( entity ) (1) (since C++11)
decltype ( expression ) (2) (since C++11)

[edit] Explanation

If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type (described in the specification of the structured binding declaration).

If the argument is an unparenthesized id-expression naming a non-type template parameter, then decltype yields the type of the template parameter (after performing any necessary type deduction if the template parameter is declared with a placeholder type). The type is non-const even if the entity is a template parameter object (which is a const object).

2) If the argument is any other expression of type T , and
a) if the value category of expression is xvalue, then decltype yields T && ;
b) if the value category of expression is lvalue, then decltype yields T & ;

If expression is a function call which returns a prvalue of class type or is a comma expression whose right operand is such a function call, a temporary object is not introduced for that prvalue.

If expression is a prvalue other than a (possibly parenthesized) immediate invocation (since C++20) , a temporary object is not materialized from that prvalue: such prvalue has no result object.

Note that if the name of an object is parenthesized, it is treated as an ordinary lvalue expression, thus decltype ( x ) and decltype ( ( x ) ) are often different types.

decltype is useful when declaring types that are difficult or impossible to declare using standard notation, like lambda-related types or types that depend on template parameters.

[edit] Notes

Feature-test macro Value Std Feature
__cpp_decltype 200707L (C++11) decltype

[edit] Keywords

[edit] Example

Run this code

#include #include #include struct A { double x; }; const A* a; decltype(a->x) y; // type of y is double (declared type) decltype((a->x)) z = y; // type of z is const double& (lvalue expression) templatetypename T, typename U> auto add(T t, U u) -> decltype(t + u) // return type depends on template parameters // return type can be deduced since C++14 { return t + u; } const int& getRef(const int* p) { return *p; } static_assert(std::is_same_vdecltype(getRef), const int&(const int*)>); auto getRefFwdBad(const int* p) { return getRef(p); } static_assert(std::is_same_vdecltype(getRefFwdBad), int(const int*)>, "Just returning auto isn't perfect forwarding."); decltype(auto) getRefFwdGood(const int* p) { return getRef(p); } static_assert(std::is_same_vdecltype(getRefFwdGood), const int&(const int*)>, "Returning decltype(auto) perfectly forwards the return type."); // Alternatively: auto getRefFwdGood1(const int* p) -> decltype(getRef(p)) { return getRef(p); } static_assert(std::is_same_vdecltype(getRefFwdGood1), const int&(const int*)>, "Returning decltype(return expression) also perfectly forwards the return type."); int main() { int i = 33; decltype(i) j = i * 2; static_assert(std::is_same_vdecltype(i), decltype(j)>); assert(i == 33 && 66 == j); auto f = [i](int av, int bv) -> int { return av * bv + i; }; auto h = [i](int av, int bv) -> int { return av * bv + i; }; static_assert(!std::is_same_vdecltype(f), decltype(h)>, "The type of a lambda function is unique and unnamed"); decltype(f) g = f; std::cout  (3, 3)  <' '  (3, 3)  <'\n'; }

decltype specifier

Проверяет объявленный тип сущности или тип и категорию значения выражения.

Syntax

decltype ( entity ) (1) (since C++11)
decltype ( expression ) (2) (since C++11)

Explanation

1) Если аргумент представляет собой выражение id-expression без скобок или выражение class member access без скобок, то decltype возвращает тип объекта, названного этим выражением. Если такой сущности нет или если аргумент называет набор перегруженных функций, программа неправильно сформирована.

Если аргумент представляет собой id-expression без скобок, именующий structured binding , то decltype дает referenced type (описано в спецификации структурированного связывания declaration).

Если аргумент представляет собой id-expression без скобок, именующий non-type template parameter , то decltype дает тип параметра шаблона (после выполнения любого необходимого вывода типа, если параметр шаблона объявлен с заполнителем type). Тип не является константным, даже если сущность является объектом параметра шаблона (который является константой object).

2) Если аргумент является любым другим выражением типа T и
а) если выражение value category равно xvalue , то decltype дает T&& ;
б) если категория значения выражения lvalue , то decltype дает T& ;
c), если значение category выражения равно prvalue , то decltype возвращает T .

Если выражение является вызовом функции, который возвращает значение prvalue типа класса или представляет собой comma expression , правым операндом которого является такой вызов функции, временный объект для этого значения prvalue не вводится.

Если выражение является значением prvalue, отличным от immediate invocation (возможно, в скобках) immediate invocation (поскольку C++20), временный объект не является materialized из этого значения prvalue: такое значение prvalue не имеет объекта результата.

Поскольку временный объект не создается, тип не обязательно должен быть complete или иметь доступный destructor , а может быть abstract . Это правило не применяется к подвыражениям: в decltype(f(g())) g() должен иметь полный тип, а f() — нет.

Обратите внимание, что если имя объекта заключено в круглые скобки, оно рассматривается как обычное выражение lvalue, поэтому decltype(x) и decltype((x)) часто являются разными типами.

decltype полезен при объявлении типов, которые трудно или невозможно объявить с использованием стандартной нотации, например типов, связанных с lambda, или типов, зависящих от параметров шаблона.

Notes

Feature-test macro Value Std Comment
__cpp_decltype 200707L (C++11) decltype

Keywords

Example

#include #include #include struct A < double x; >; const A* a; decltype(a->x) y; // тип y — double (объявленный тип) decltype((a->x)) z = y; // тип z - const double& (выражение lvalue) templatetypename T, typename U> auto add(T t, U u) -> decltype(t + u) // тип возврата зависит от параметров шаблона // тип возвращаемого значения можно вывести, начиная с C++14 < return t + u; > const int& getRef(const int* p) < return *p; > static_assert(std::is_same_vconst int&(const int*)>); auto getRefFwdBad(const int* p) < return getRef(p); > static_assert(std::is_same_vint(const int*)>, "Just returning auto isn't perfect forwarding."); decltype(auto) getRefFwdGood(const int* p) < return getRef(p); > static_assert(std::is_same_vconst int&(const int*)>, "Returning decltype(auto) perfectly forwards the return type."); // Alternatively: auto getRefFwdGood1(const int* p) -> decltype(getRef(p)) < return getRef(p); > static_assert(std::is_same_vconst int&(const int*)>, "Returning decltype(return expression) also perfectly forwards the return type."); int main() < int i = 33; decltype(i) j = i * 2; static_assert(std::is_same_v); assert(i == 33 && 66 == j); auto f = [i](int a, int b) -> int < return a * b + i; >; auto h = [i](int a, int b) -> int < return a * b + i; >; static_assert(!std::is_same_v, "The type of a lambda function is unique and unnamed"); decltype(f) g = f; std::cout 3, 3) ' ' 3, 3) '\n'; >

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *