© Po Yen Chen 2021
Background image ©
Po Yen Chen @poyenc
the little essential things
C++20 template and constraints
Outline
-
Templates
-
SFINAE
-
Detection Idiom
-
C++20 Concepts
Templates
-
Complement of overloads
-
Backup of overloads
-
Always has constraints
Templates - Complement of overloads
int max(int lhs, int rhs) {
return (lhs < rhs ? rhs : lhs);
}
double max(double lhs, double rhs) {
return (lhs < rhs ? rhs : lhs);
}
int main() {
max(1, 1);
max(2., 2.);
max(3, 4.);
}Any issues in following code snippet?
Templates - Complement of overloads (Cont.)
#include <type_traits>
int max(int lhs, int rhs) {
return (lhs < rhs ? rhs : lhs);
}
double max(double lhs, double rhs) {
return (lhs < rhs ? rhs : lhs);
}
template <typename Left, typename Right>
auto max(Left lhs, Right rhs) -> std::common_type_t<Left, Right> {
return (lhs < rhs ? rhs : lhs);
}
int main() {
max(1, 1);
max(2., 2.);
max(3, 4.);
}Templates - Backup of overloads
#include <utility>
namespace lib {
class Point {
int x_, y_;
public:
explicit Point(int x, int y) : x_(x), y_(y) { }
Point(Point&&) = delete;
};
} // namespace lib
int main() {
lib::Point p(1, 2), p2(3, 4);
std::swap(p, p2);
}Any issues in following code snippet?
Templates - Backup of overloads (Cont.)
#include <utility>
namespace lib {
class Point {
// other code goes here
void swap(Point& rhs) {
std::swap(x_, rhs.x_);
std::swap(y_, rhs.y_);
}
};
void swap(Point& lhs, Point& rhs) {
lhs.swap(rhs);
}
} // namespace lib
int main() {
lib::Point p(1, 2), p2(3, 4);
using std::swap;
swap(p, p2);
}Templates - Always has constraints
#include <string>
int twice(int value) { return value * 2; }
double twice(double value) { return value * 2; }
template <typename Value>
Value twice(Value value) { return value * 2; }
int main() {
using namespace std::string_literals;
twice(1);
twice(2.);
twice("3"s);
}Any issues in following code snippet?
SFINAE
-
std::enable_if
-
Proper location to prevent instantiation
SFINAE - std::enable_if
#include <iostream>
#include <type_traits>
// 1. check declaration syntax
template <
typename Value,
typename = std::enable_if_t<std::is_unsigned_v<Value>>
>
Value twice(Value value) {
// 2. instantiate template by template parameters
return value << 1;
}
int main() {
std::cout << twice(2u) << std::endl; // print 4
}Two-phase name lookup
SFINAE - Proper location to prevent instantiation
// option #1
template <
typename Value, typename = std::enable_if_t<std::is_unsigned_v<Value>>
>
Value twice(Value value) {
return value << 1;
}
// option #2
template <typename Value>
auto twice(Value value) -> std::enable_if_t<std::is_unsigned_v<Value>, Value> {
return value << 1;
}
// option #3
template <typename Value>
Value twice(Value value, std::enable_if_t<std::is_unsigned_v<Value>>* = nullptr) {
return value << 1;
}Detection Idiom
-
Pre-C++11 detection idiom
-
decltype()
-
Write own type traits
Detection Idiom - Pre-C++11 detection idiom
detect member function size()
#include <iostream>
template <typename T>
class has_size {
typedef char yes_type[1];
typedef char no_type[2];
template <typename U, U> struct check;
template <typename U>
static yes_type& test(check<std::size_t (U::*)() const, &U::size>*);
template <typename U>
static no_type& test(...);
public:
enum { value = sizeof(test<T>(NULL)) == sizeof(yes_type) };
};
struct Foo {};
struct Bar { std::size_t size(); };
int main() {
std::cout << has_size<std::string>::value << std::endl; // print 1
std::cout << has_size<Foo>::value << std::endl; // print 0
std::cout << has_size<Bar>::value << std::endl; // print 0
}Detection Idiom - decltype()
#include <iostream>
#include <string>
#include <utility>
template <typename PairLike>
auto print(std::ostream& stream, const PairLike& p)
-> std::enable_if_t<
std::is_same_v<decltype(stream << p.first), std::ostream&>
&& std::is_same_v<decltype(stream << p.second), std::ostream&>,
decltype(stream << p.first, stream << p.second, stream)
> {
stream << "(" << p.first << ", " << p.second << ")";
return stream;
}
struct MyPair {
double first;
char second;
};
int main() {
const MyPair mp = {1., '2'};
print(std::cout, mp) << std::endl;
}detect any desired expressions
Detection Idiom - Write own type traits
template <typename T, typename = void>
struct is_printable_pair : std::false_type {};
template <typename T>
struct is_printable_pair<
T,
std::void_t<
decltype(std::declval<std::ostream&>() << std::declval<const T&>().first,
std::declval<std::ostream&>() << std::declval<const T&>().second)
>
>
: std::bool_constant<
std::is_same_v<
decltype(std::declval<std::ostream&>() << std::declval<const T&>().first),
std::ostream&>
&& std::is_same_v<
decltype(std::declval<std::ostream&>() << std::declval<const T&>().second),
std::ostream&>
> {};
template <typename PairLike>
auto print(std::ostream& stream, const PairLike& p)
-> std::enable_if_t<is_printable_pair<PairLike>::value, decltype(stream)> {
stream << "(" << p.first << ", " << p.second << ")";
return stream;
}C++20 Concepts
-
requires clause
-
Toward modern thinking
C++20 Concepts - requires clause
template <typename PairLike>
requires is_printable_pair<PairLike>
std::ostream& print(std::ostream& stream, const PairLike& p) {
stream << "(" << p.first << ", " << p.second << ")";
return stream;
}separate constraints from return type
C++20 Concepts - Toward modern thinking
#include <iostream>
#include <memory>
struct Animal {
virtual ~Animal() = default;
virtual void makeNoise() const = 0;
};
struct Dog : Animal {
void makeNoise() const override { std::cout << "woof" << std::endl; }
};
struct Cat : Animal {
void makeNoise() const override { std::cout << "meow" << std::endl; }
};
std::unique_ptr<Animal> getAnimal(bool choice) {
if (choice) {
return std::make_unique<Dog>();
}
return std::make_unique<Cat>();
}
int main() {
bool choice;
std::cin >> choice;
const std::unique_ptr<Animal> animal = getAnimal(choice);
animal->makeNoise();
}write generic code everywhere (example)
C++20 Concepts - Toward modern thinking (Cont.)
#include <iostream>
template <typename T>
concept Animal = requires (const T& object) { object.makeNoise(); };
struct Dog { void makeNoise() const { std::cout << "woof" << std::endl; } };
struct Cat { void makeNoise() const { std::cout << "meow" << std::endl; } };
template <bool B>
using getAnimal = std::conditional_t<B, Dog, Cat>;
int main() {
const Animal auto animal = getAnimal<true>();
animal.makeNoise();
}write generic code everywhere (example)
Questions
C++20 template and constraints
By Po-Yen, Chen
C++20 template and constraints
- 254