decltype
Иногда возвращаемый тип не хочется писать руками:
int f();
??? g() {
return f();
}
В C++11 появилась конструкция, позволяющая по выражению узнать его тип:
int main() {
decltype(2 + 2) a = 42; // int a = 42;
}
decltype(f()) g() {
return f();
}
decltype
сделан так, чтобы его было удобно использовать для возврата значений:
int foo();
int& bar();
int&& baz();
// decltype(foo()) -> int
// decltype(bar()) -> int&
// decltype(baz()) -> int&&
То есть decltype(expr)
возвращает следующие типы:
- type для
prvalue
- type& для
lvalue
- type&& для
xvalue
Также ключевое слово decltype
применимо для переменных и членов класса.
struct x {
int a;
};
int main() {
decltype(x::a) y; // int y
int a;
decltype(a) b = 42; // int b = 42
decltype((a)) c = a; // int & c = a
}
Последнее работает так, потому что (a)
- это выражение, и оно имеет тип int&, а a
- это имя переменной.
declval
Иногда хочется узнать тип чего-то, что зависит от шаблонных аргументов функции, но просто сделать это с помощью decltype
не получится, так как тогда компилятор встречает обращение к параметру функции, когда еще не дошел до его объявления.
Для этого есть синтаксическая конструкция declval
:
int foo(int);
float foo(float);
// compile error
template <typename T>
decltype(foo(t)) f(T&& t) {
return foo(std::forward<T>(t));
}
// nice.
template <typename T>
decltype(foo(declval<T>())) f(T&& t) {
return foo(std::forward<T>(t));
}
Сигнатура declval
могла бы выглядеть как-то так:
template <typename T>
T declval();
Для declval
не нужно тело функции, так как decltype
не генерирует машинный код и считается на этапе компиляции.
В языке есть несколько мест с похожей логикой - например, sizeof
. Такие места называются unevaluated contexts.
При использовании сигнатуры, как выше, могут возникать проблемы с неполными типами (просто не скомпилируется). Это происходит из-за того, что если функция возвращает структуру, то в точке, где вызывается функция, эта структура должно быть complete типом. Чтобы обойти это, делают возвращаемый тип rvalue-ссылкой:
template <typename T>
T&& declval();
Trailing return types
Чтобы не писать declval
, сделали возможной следующую конструкцию:
template <typename... Args>
auto f(Args&&... args) -> decltype(foo(std::forward<Args>(args)...)) {
return foo(std::forward<Args>(args)...);
}
То есть компилятор, натыкаясь на decltype
уже знает аргументы, которые передаются в функцию, и может на них опираться. Очень удобно, конечно (сарказм).
auto
Можно заметить, что в return
и decltype
повторяется одно и то же выражение. Во избежание копипасты добавили возможность писать decltype(auto)
.
int main() {
decltype(auto) b = 2 + 2; // int b = 2 + 2;
}
template <typename... Args>
decltype(auto) f(Args&&... args) {
return foo(std::forward<Args>(args)...);
}
Возникает вопрос, а зачем нам вообще decltype
, можно ли заменить его на просто auto
? Для этого стоит сказать о том, как работает auto
.
Правило вывода типов у auto
почти полностью совпадает с тем, как выводятся шаблонные параметры. Поэтому auto
отбрасывает ссылки и cv-квалификаторы.
int& bar();
int main() {
auto c = bar(); // int c = bar()
auto& c = bar(); // int& c = bar()
}
Поэтому обычный auto
в возвращаемом типе отбрасывает ссылки с cv-квалификаторами, поэтому чаще всего нам нужен именно decltype(auto)
.
Еще стоит сказать, что если у функции несколько инструкций return
, который выводятся в разные типы, то использовать decltype
и auto
нельзя:
// compile error
auto f(bool flag) {
if (flag) {
return 1;
} else {
return 1u;
}
}
Заимствования:
cpp-notes/18_decltype_auto_nullptr.md at master · lejabque/cpp-notes (github.com)