decltype(e)
Non-simplified set of rules can be found here.
Examples include:
int i;
int j& = i;
decltype(i) x; // int; - variable
decltype(j) y; // int& - lvalue
decltype(5); // int - prvalue
Iterator used over templated collection and returns a reference to an item at a particular index
template <typename It>
??? find(It beg, It end, int index) {
for (auto it = beg, int i = 0; beg != end; ++it; ++i) {
if (i == index) {
return *it;
}
}
return end;
}
We know the return type should be decltype(*beg), since we know the type of what is returned is of type *beg
This will not work, as beg is not declared until after the reference to beg
template <typename It>
decltype(*beg) find(It beg, It end, int index) {
for (auto it = beg, int i = 0; beg != end; ++it; ++i) {
if (i == index) {
return *it;
}
}
return end;
}
Introduction of C++11 Trailing Return Types solves this problem for us
template <typename It>
auto find(It beg, It end, int index) -> decltype(*beg) {
for (auto it = beg, int i = 0; beg != end; ++it; ++i) {
if (i == index) {
return *it;
}
}
return end;
}
A number of add, remove, and make functions exist as part of type traits that provide an ability to transform types
A number of add, remove, and make functions exist as part of type traits that provide an ability to transform types
#include <iostream>
#include <type_traits>
template<typename T1, typename T2>
void print_is_same() {
std::cout << std::is_same<T1, T2>() << std::endl;
}
int main() {
std::cout << std::boolalpha;
print_is_same<int, int>();
// true
print_is_same<int, int &>(); // false
print_is_same<int, int &&>(); // false
print_is_same<int, std::remove_reference<int>::type>();
// true
print_is_same<int, std::remove_reference<int &>::type>(); // true
print_is_same<int, std::remove_reference<int &&>::type>(); // true
print_is_same<const int, std::remove_reference<const int &&>::type>(); // true
}
A number of add, remove, and make functions exist as part of type traits that provide an ability to transform types
#include <iostream>
#include <type_traits>
int main() {
typedef std::add_rvalue_reference<int>::type A;
typedef std::add_rvalue_reference<int&>::type B;
typedef std::add_rvalue_reference<int&&>::type C;
typedef std::add_rvalue_reference<int*>::type D;
std::cout << std::boolalpha
std::cout << "typedefs of int&&:" << "\n";
std::cout << "A: " << std::is_same<int&&, A>>::value << "\n";
std::cout << "B: " << std::is_same<int&&, B>>::value << "\n";
std::cout << "C: " << std::is_same<int&&, C>>::value << "\n";
std::cout << "D: " << std::is_same<int&&, D>>::value << "\n";
}
lvalue | const lvalue | rvalue | const rvalue | |
---|---|---|---|---|
template T&& | Yes | Yes | Yes | Yes |
T& | Yes | |||
const T& | Yes | Yes | Yes | Yes |
T&& | Yes |
Note:
#include <iostream>
void print(const std::string& a) {
std::cout << a << "\n";
}
const std::string goo() {
return "C++";
}
int main() {
std::string j = "C++";
const std::string& k = "C++";
foo("C++"); // rvalue
foo(goo()); // rvalue
foo(j); // lvalue
foo(k); // const lvalue
}
#include <iostream>
template <typename T>
void print(T&& a) {
std::cout << a << "\n";
}
const std::string goo() {
return 5;
}
int main() {
int j = 1;
const int &k = 1;
foo(1); // rvalue, foo(int&&)
foo(goo()); // rvalue foo(const int&&)
foo(j); // lvalue foo(int&)
foo(k); // const lvalue foo(const int&)
}
template <typename T>
auto wrapper(T value) {
return fn(value);
}
template <typename T>
auto wrapper(T value) {
return fn(value);
}
template <typename T>
auto wrapper(const T& value) {
return fn(value);
}
template <typename T>
auto wrapper(const T& value) {
return fn(value);
}
// Calls fn(x)
// Should call fn(std::move(x))
wrapper(std::move(x));
template <typename T>
auto wrapper(T&& value) {
// pseudocode
return fn(value is lvalue ? value : std::move(value));
}
wrapper(std::move(x));
This solves our previous problem, but we still need to come up with a function that matches the pseudocode
template <typename T>
auto wrapper(T&& value) {
return fn(std::forward<T>(value));
}
wrapper(std::move(x));
// This is approximately std::forward.
template <typename T>
T& forward(T& value) {
return static_cast<T&>(value);
}
template <typename T>
T&& forward(T&& value) {
return static_cast<T&&>(value);
}
template <typename... Args>
auto wrapper(Args&&... args) {
// Note that the ... is outside the forward call, and not right next to args.
// This is because we want to call
// fn(forward(arg1), forward(arg2), ...)
// and not
// fn(forward(arg1, arg2, ...)
return fn(std::forward<Args>(args)...);
}
The only real use for std::forward is when you want to wrap a function. This could be because:
template <typename T>
auto wrapper(T value) {
return fn(value);
asdfadsfasdf