decltype(e)
Non-simplified set of rules can be found here.
Examples include:
int i;
int j& = i;
decltype(i) x; // int - variable
decltype((i)) z; // int - lvalue
decltype(j) y; // int& - variable
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>
auto print_is_same() -> void {
std::cout << std::is_same<T1, T2>() << "\n";
}
auto main() -> int {
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
}
demo850-transform.cpp
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>
auto main() -> int {
using A = std::add_rvalue_reference<int>::type;
using B = std::add_rvalue_reference<int&>::type;
using C = std::add_rvalue_reference<int&&>::type;
using D = std::add_rvalue_reference<int*>::type;
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";
}
Since C++14/C++17 you can use shortened type trait names.
#include <iostream>
#include <type_traits>
auto main() -> int {
using A = std::add_rvalue_reference<int>;
using B = std::add_rvalue_reference<int>;
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";
}
lvalue | const lvalue | rvalue | const rvalue | |
---|---|---|---|---|
template T&& | Yes | Yes | Yes | Yes |
T& | Yes | |||
const T& | Yes | Yes | Yes | Yes |
T&& | Yes |
Note:
Arguments
Parameters
#include <iostream>
auto print(const std::string& a) -> void{
std::cout << a << "\n";
}
auto goo() -> std::string const {
return "C++";
}
auto main() -> int {
std::string j = "C++";
std::string const& k = "C++";
print("C++"); // rvalue
print(goo()); // rvalue
print(j); // lvalue
print(k); // const lvalue
}
#include <iostream>
template <typename T>
auto print(T&& a) -> void {
std::cout << a << "\n";
}
auto goo() -> int const {
return 5;
}
auto main() -> int {
int j = 1;
int const& k = 1;
print(1); // rvalue, foo(int&&)
print(goo()); // rvalue foo(const int&&)
print(j); // lvalue foo(int&)
print(k); // const lvalue foo(const int&)
}
demo851-bind1.cpp
demo852-bind2.cpp
int n;
int& lvalue = n; // Lvalue reference
int&& rvalue = std::move(n); // Rvalue reference
template <typename T> T&& universal = n; // This is a universal reference.
auto&& universal_auto = n; // This is the same as the above line.
template<typename T>
void f(T&& param); // Universal reference
template<typename T>
void f(std::vector<T>&& param); // Rvalue reference (read the rules carefully).
If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a forwarding reference (AKA universal reference in some older texts).
For more details on forwarding references, see this blog post
Attempt 1: Take in a value
What's wrong with this?
template <typename T>
auto wrapper(T value) {
return fn(value);
}
template <typename T>
auto wrapper(T const& value) {
return fn(value);
}
Attempt 2: Take in a const reference
What's wrong with this?
What happens if we pass in an rvalue?
// Calls fn(x)
// Should call fn(std::move(x))
wrapper(std::move(x));
What happens if wrapper needs to modify value?
Code fails to compile
template <typename T>
auto wrapper(T& value) {
return fn(value);
}
Attempt 3: Take in a mutable reference
What's wrong with this?
What happens if we pass in a const object?
What happens if we pass in an rvalue?
const int n = 1;
wrapper(n);
wrapper(1)
template <typename T>
auto wrapper(T&& value) {
return fn(value);
}
Attempt 4: Forwarding references
What's wrong with this?
// Instantiation generated
template <>
auto wrapper<int&>((int&)&& value) {
return fn(value);
}
// Collapses to
template <>
auto wrapper<int&>(int& value) {
return fn(value);
}
int i;
wrapper(i);
// Instantiation generated
auto wrapper<int&&>((int&&)&& value) {
return fn(value);
}
// Collapses to
auto wrapper<int&&>(int&& value) {
return fn(value);
}
int i;
wrapper(std::move(i));
Calls fn(i)
Also calls fn(i)
The parameter is an rvalue, but inside the function, value is an lvalue
Attempt 4: Forwarding references
We want to generate this
// We want to generate this.
auto wrapper<int&>(int& value) {
return fn(static_cast<int&>(value));
}
// We want to generate this
auto wrapper<int&&>(int&& value) {
return fn(static_cast<int&&>(value));
}
It turns out there's a function for this already
template <typename T>
auto wrapper(T&& value) {
return fn(std::forward<T>(value));
// Equivelantly (don't do this, forward is easier to read).
return fn(static_cast<T&&>(value));
}
template <typename T, typename... Args>
auto make_unique(Args&&... args) -> std::unique_ptr<T> {
// Note that the ... is outside the forward call, and not right next to args.
// This is because we want to call
// new T(forward(arg1), forward(arg2), ...)
// and not
// new T(forward(arg1, arg2, ...))
return std::unique_ptr(new T(std::forward<Args>(args)...));
}
The only real use for std::forward is when you want to wrap a function with a parameterized type. This could be because: