COMP6771 Week 8.2
Advanced Types
decltype
decltype(e)
- Semantic equivalent of a "typeof" function for C++
-
Rule 1:
- If expression e is any of:
- variable in local scope
- variable in namespace scope
- static member variable
- function parameters
- then result is variable/parameters type T
- If expression e is any of:
- Rule 2: if e is an lvalue, result is T&
- Rule 3: if e is an xvalue, result is T&&
- Rule 4: if e is a prvalue, result is T
Non-simplified set of rules can be found here.
decltype
Examples include:
int i;
int j& = i;
decltype(i) x; // int; - variable
decltype(j) y; // int& - lvalue
decltype(5); // int - prvalue
Determining return types
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
Determining return types
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;
}
Type Transformations
A number of add, remove, and make functions exist as part of type traits that provide an ability to transform types
Type Transformations
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
}
Type Transformations
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";
}
Binding
lvalue | const lvalue | rvalue | const rvalue | |
---|---|---|---|---|
template T&& | Yes | Yes | Yes | Yes |
T& | Yes | |||
const T& | Yes | Yes | Yes | Yes |
T&& | Yes |
Note:
- const T& binds to everything!
- template T&& binds to everything!
- template <typename T> void foo(T&& a);
Examples
#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&)
}
Forwarding functions
template <typename T>
auto wrapper(T value) {
return fn(value);
}
- What's wrong with this?
- What can we do about it?
Forwarding functions
template <typename T>
auto wrapper(T value) {
return fn(value);
}
- What's wrong with this?
- What can we do about it?
- Pass in a reference?
Forwarding functions
template <typename T>
auto wrapper(const T& value) {
return fn(value);
}
- This solves our previous problem
- But, it creates a new problem. What is it?
- What can we do about it?
Forwarding functions
template <typename T>
auto wrapper(const T& value) {
return fn(value);
}
// Calls fn(x)
// Should call fn(std::move(x))
wrapper(std::move(x));
- Problem: Won't work if fn takes in rvalues
- What can we do about it?
- Make a seperate rvalue definition
- Try template T&&, which binds to everything correctly
Forwarding functions
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
std::forward
template <typename T>
auto wrapper(T&& value) {
return fn(std::forward<T>(value));
}
wrapper(std::move(x));
- Returns reference to value for lvalues
- Returns std::move(value) for rvalues
// 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);
}
std::forward and variadic templates
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)...);
}
- Often you need to call a function you know nothing about
- It may have any amount of parameters
- Each parameter may be a different unknown type
- Each parameter may be an lvalue or rvalue
uses of std::forward
The only real use for std::forward is when you want to wrap a function. This could be because:
- You want to do something else before or after (eg. std::make_unique / std::make_shared need to wrap it in the unique/shared_ptr variable)
- You want to do something slightly different (eg. std::vector::emplace uses uninitialised memory construction)
- You want to add an extra parameter (eg. always call a function with the last parameter as 1). This isn't usually very useful though, because it can be achieved with std::bind or lambda functions.
dsfsdfsdfsdf
template <typename T>
auto wrapper(T value) {
return fn(value);
asdfadsfasdf
- What's wrong with this?dsfasdf
COMP6771 19T2 - 8.2 - Advanced Types
By cs6771
COMP6771 19T2 - 8.2 - Advanced Types
- 723