© 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