A placeholder type specifier designates a placeholder type that will be replaced later, typically by deduction from an initializer.
-
Since C++11
-
Позволяет иногда не писать тип создаваемого объекта
-
for(auto it = map.begin(); it != map.end(); ++it) { ... } -
for(auto item : map) { ... } -
Можно навешивать ссылки и константность
const auto& y = x;- Ссылки вообще всегда обрезаются
auto&&работает как универсальная ссылка
- Type is deduced using the rules for template argument deduction
auto y = x;is the same thatf(T value) <= f(x);
- Отличие только одно:
autoможет выводитьstd::initializer_list
template <typename T>
void foo(T arg) {}
int main() {
foo({1, 2, 3}); // CE
auto t = {1, 2, 3}; // std::inializer_list
}- Also see DLI, CLI (C++17)
- Since C++14
- Очень полезно, когда тип большой или его нельзя выразить явно
- Например, фабрика лямбда-функций
auto f(int& x) {
return x;
}
int main() {
int v = 1;
auto x = f(v); // int
auto& y = f(v); // CE (lvalue link bind to rvalue int)
auto&& z = f(v); // int&&
}auto h(); // OK since C++14: h’s return type will be deduced when it is defined- Неоднозначность
- Будет
CE - Даже если можно неявно привести один тип к другому (никто не приоритизировал)
- Будет
auto f(int& x) {
if (x > 0) {
return x;
}
return 0u;
} // CEif constexprразрешается доauto
template <typename T>
auto f(int& x) {
if constexpr (std::is_same_v<T, int>) {
return x;
} else {
return 1.0;
}
}- Синтаксис, позволяющий писать тип возвращаемого объекта по-другому
- Полезно, когда этот тип зависит от аргументов
- Тут честно берется
int
auto f(int x) -> int {
return x;
}- Since C++17
- Буквально сокращение шаблонов
- То есть с
std::initializer_listне прокатит
- То есть с
#include <iostream>
void f(auto&& x) { // сокращение шаблонов
std::cout << __PRETTY_FUNCTION__ << '\n';
}
int main() {
f(1); // void f(auto:16&&) [with auto:16 = int]
// f({1, 2, 3}); // CE
}- Можно с вариадиками
void f(auto... x) {
}-
Also see
autotemplates in Templates page -
Также можно создавать шаблонные переменные
-
Since C++20
#include <iostream>
template <auto X>
auto cringe = X;
int main() {
std::swap(cringe<1>, cringe<2>);
}#include <iostream>
#include <string_view>
auto foo(int x) {} // C++14
auto bar(auto y) {} // C++17
template <auto X>
void baz() {} // C++20
template <typename T> // => <T, auto, auto>
void g(auto... x, T y) {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <auto X>
auto var = X;
void f(auto x) { std::cout << __PRETTY_FUNCTION__ << '\n'; }
template <typename T>
struct S {
T x;
};
int main() {
[](auto var) {}; // C++14
auto x = 10; // C++11
bar<int>(10);
g<int, double, double>(0, 0, 0);
baz<2.2>();
f(var<10>);
f(var<2.2>);
f(var<'a'>);
std::cout << var<10> << '\n';
var<10> = 100;
std::cout << var<10> << '\n';
std::swap(var<1>, var<2>);
// var<S{10}>; CE ?! TODO
}- Since C++11
- Метафункция, которая в compile-time возвращает тип выражения
int x;
decltype(x) u = x;То есть decltype возвращает точный тип (с сохранением ссылок)
Есть 2 типа decltype:
decltype(entity)
int x;
decltype(x) y; // statement - смотрим на тип объекта
// y = [int]decltype(expression)
int x;
decltype((x)) y; // expression
// y = [int&]Вывод типов в decltype(expr) работает по следующим правилам:
- if the value category of expression is xvalue, then
decltypeyieldsT&&;decltype(std::move(x))
- if the value category of expression is lvalue, then
decltypeyieldsT&;decltype(++x)
- if the value category of expression is prvalue, then
decltypeyieldsT.decltype(x++)
- Можно также всякое навешивать
int x;
decltype(x)& y = x;
decltype(x)* yy = &x;
decltype(x)&& = std::move(x); // не универсальная ссылкаtemplate <typename T>
void f() = delete;
int main() {
int x;
int& y = x;
const decltype(y) z = x;
f<decltype(z)>(); // T = T&
}- Это происходит потому что
constнавешивается не на то, что лежит под ссылкой, а на саму ссылку. А ссылки и так нельзя перевешивать (т.е. они и так константные)
- Код внутри
decltypeне выполняется - это compile-time конструкция- Он просто возьмет из сигнатуры выражения возвращаемый тип
int x = 0;
decltype(++x) y = x; // y = [int&]
++y;
std::cout << x << y; // 11decltype(throw 1)* p = nullptr; // void* p = nullptr;- Хотим всегда возвращать тип такой, какой вернет
some_func, но допустим чтоsome_funcумеет возвращать ссылку или не ссылку для некоторых типов - Поэтому
autoне поможет (срезает ссылки)
template <typename T>
... g(const T& x) {
return some_func(x);
}decltypeтоже не поможет, ткxеще не объявлен
template <typename T>
decltype(some_func(x)) g(const T& x /* point of decl. here */) {
return some_func(x)
}- Solution - trailing return type
template <typename T>
auto g(const T& x) -> decltype(some_func(x)) {
return some_func(x);
}- Не всегда обращение к контейнеру по индексу возвращает ссылку (
std::vector<bool>), поэтому приходится вот так извращаться с возвращаемым типом
template <typename Container>
auto get(const Container& c, size_t i) -> decltype(c[i]) {
return c[i];
}- С C++14
- Механизм выводить тип не по правилам
auto, а по правиламdecltypestatement/expressionтакже определяется
template <typename Container>
decltype(auto) get(const Container& c, size_t i) {
return c[i];
}int x = 0;
int& y = x;
decltype(auto) z = y; // int&- С C++17
int main() {
std::pair<int, std::string> p(5, "abc");
auto [a, b] = p;
std::cout << a << ' ' << b << '\n';
}- Работает для tuple-like, C'шных массивов, data members
struct Test { ... };
int main() {
Test t;
auto& [s1, s2, a, v1] = t;
}struct Test {
std::vector<int> v1;
std::string s1;
std::deque<int> d1;
std::map<int, int> m1;
bool IsEmpty() const {
// return v1.empty() && s1.empty() && d1.empty() && m1.empty(); // best solution
auto& [a1, a2, a3, a4] = *this;
return [](auto&&... args) -> bool { return (args.empty() && ...); }(a1, a2, a3, a4);
}
};-
For initializer syntax
... = expression, the elements are copy-initialized -
For initializer syntaxes
...()or...{}, the elements are direct-initialized
We use E to denote the type of the identifier expression e (i.e., E is the equivalent of std::remove_reference_t<decltype((e))>).
A structured binding size of E is the number of structured bindings that need to be introduced by the structured binding declaration.
A structured binding declaration performs the binding in one of three possible ways, depending on E:
- Case 1: If
Eis an array type, then the names are bound to the array elements. - Case 2: If
Eis a non-union class type andstd::tuple_size<E>is a complete type with a member namedvalue(regardless of the type or accessibility of such member), then the "tuple-like" binding protocol is used. - Case 3: If
Eis a non-union class type butstd::tuple_size<E>is not a complete type, then the names are bound to the accessible data members ofE.
Each structured binding in the sb-identifier-list becomes the name of an lvalue that refers to the corresponding element of the array. The structured binding size of E is equal to the number of array elements.
The referenced type for each structured binding is the array element type. Note that if the array type E is cv-qualified, so is its element type.
int a[2] = {1, 2};
auto [x, y] = a; // creates e[2], copies a into e,
// then x refers to e[0], y refers to e[1]
auto& [xr, yr] = a; // xr refers to a[0], yr refers to a[1]The expression std::tuple_size<E>::value must be a well-formed integral constant expression, and the structured binding size of E is equal to std::tuple_size<E>::value.
For each structured binding, a variable whose type is "reference to std::tuple_element<I, E>::type" is introduced: lvalue reference if its corresponding initializer is an lvalue, rvalue reference otherwise. The initializer for the Ith variable is:
e.get<I>(), if lookup for the identifiergetin the scope ofEby class member access lookup finds at least one declaration that is a function template whose first template parameter is a non-type parameter- Otherwise,
get<I>(e), where get is looked up by argument-dependent lookup only, ignoring non-ADL lookup.
In these initializer expressions, e is an lvalue if the type of the entity e is an lvalue reference (this only happens if the ref-qualifier is & or if it is && and the initializer expression is an lvalue) and an xvalue otherwise (this effectively performs a kind of perfect forwarding), I is a std::size_t prvalue, and <I> is always interpreted as a template parameter list.
The structured binding then becomes the name of an lvalue that refers to the object bound to said variable.
The referenced type for the Ith structured binding is std::tuple_element<I, E>::type.
Every non-static data member of E must be a direct member of E or the same base class of E, and must be well-formed in the context of the structured binding when named as e.name. E may not have an anonymous union member. The structured binding size of E is equal to the number of non-static data members.
Each structured binding in sb-identifier-list becomes the name of an lvalue that refers to the next member of e in declaration order (bit-fields are supported); the type of the lvalue is that of e.mI, where mI refers to the Ith member.
The referenced type of the Ith structured binding is the type of e.mI if it is not a reference type, or the declared type of mI otherwise.
#include <iostream>
struct S {
mutable int x1 : 2;
volatile double y1;
};
S f() { return S{1, 2.3}; }
int main() {
const auto [x, y] = f(); // x is an int lvalue identifying the 2-bit bit-field
// y is a const volatile double lvalue
std::cout << x << ' ' << y << '\n'; // 1 2.3
x = -2; // OK
// y = -2.; // Error: y is const-qualified
std::cout << x << ' ' << y << '\n'; // -2 2.3
}Let valI be the object or reference named by the Ith structured binding in sb-identifier-list :
- The initialization of
eis sequenced before the initialization of anyvalI. - The initialization of each
valIis sequenced before the initialization of anyvalJwhereIis less thanJ.
- Source
- Example for:
class Person {
public:
std::string name;
int age;
};- Include
<utility>. - Specialize the
std::tuple_sizeso that itsvalueis astd::size_tintegral constant that says how many pieces there are.
namespace std {
template<>
struct tuple_size<::Person> {
static constexpr size_t value = 2;
};
}namespace std {
template<>
struct tuple_size<::Person>
: integral_constant<size_t, 2> {};
}- Specialize the
std::tuple_elementso that it identifies the type of each piece. You need as many specializations as you have pieces you declared in Step 2. The indices start at zero.
namespace std {
template<>
struct tuple_element<0, ::Person> {
using type = std::string;
};
template<>
struct tuple_element<1, ::Person> {
using type = int;
};
}namespace std {
template<size_t Index>
struct tuple_element<Index, ::Person>
: conditional<Index == 0, std::string, int> {
static_assert(Index < 2,
"Index out of bounds for Person");
};
}namespace std {
template<size_t Index>
struct tuple_element<Index, ::Whatever>
: tuple_element<Index, tuple<std::string, int, whatever>> {};
}- Provide all of the
getfunctions.
class Person {
public:
std::string name;
int age;
template<std::size_t Index>
auto&& get() & { return get_helper<Index>(*this); }
template<std::size_t Index>
auto&& get() && { return get_helper<Index>(*this); }
template<std::size_t Index>
auto&& get() const & { return get_helper<Index>(*this); }
template<std::size_t Index>
auto&& get() const && { return get_helper<Index>(*this); }
private:
template<std::size_t Index, typename T>
auto&& get_helper(T&& t) {
static_assert(Index < 2, "Index out of bounds for Custom::Person");
if constexpr (Index == 0) return std::forward<T>(t).name;
if constexpr (Index == 1) return std::forward<T>(t).age;
}
};template<std::size_t Index, typename T>
auto&& Person_get_helper(T&& p) {
static_assert(Index < 2,
"Index out of bounds for Custom::Person");
if constexpr (Index == 0) return std::forward<T>(t).name;
if constexpr (Index == 1) return std::forward<T>(t).age;
}
template<std::size_t Index>
auto&& get(Person& p) {
return Person_get_helper<Index>(p);
}
template<std::size_t Index>
auto&& get(Person const& p) {
return Person_get_helper<Index>(p);
}
template<std::size_t Index>
auto&& get(Person&& p) {
return Person_get_helper<Index>(std::move(p));
}
template<std::size_t Index>
auto&& get(Person const&& p) {
return Person_get_helper<Index>(move(p));
}Person p;
auto&& [name, age] = p;
name = "Fred";
age = 42;template <auto t = [](){}>
constexpr int f() { return 1 + 1; }
int main() {
f();
f();
f(); // Disable caching
return 0;
}template <auto = []() {}>
struct S {
S() = default;
~S() = default;
};
int main() {
S s1, s2;
S s3;
}
- See also about loopholes (please don't do this)