Skip to content

Concepts

  • concepts put semantic constraints on template parameters
  • requirements for template parameters are part of the interface
  • concepts are compile time predicates, i.e. a function executed at compile time and returning a bool
  • improved error messages (compiler detects not fulfilled requirements for given template parameters)
  • there are a number of predefined concepts or you can define your own
  • simplified template definition: a function using a unconstrained placeholder (auto) or a constrained placeholder (concept) automatically becomes a template
  • whereever you use auto you can also use a concept
  • concepts add no runtime costs
  • using concepts can avoid unwanted / unexpected type conversions (e.g. bool to int) within template code
  • concepts allow working with generic code at a higher level of abstraction

Syntax for using a concept

#include <concepts>

// Requires clause
template<typename T>
requires std::integral<T>
auto SomeFunc(T p)
{..}

// Trailing requires clause
template<typename T>
auto SomeFunc(T p) requires std::integral<T>
{..}

// Constrained template parameter
template<std::integral T>
auto SomeFunc(T p)
{..}

// Abbreviated function template (has no explicit "template" instruction)
auto SomeFunc(std::integral auto a)
{..}

// Concept as return type of a function
template<typename T>
requires std::integral<T>
std::integral auto SomeFunc(T p) 
{..}

// Use of specialization and overloading to automatically select the most specific type
template<typename I> void DoSomething(I& iter){..}
template<std::forward_iterator I> void DoSomething(I& iter){..}
template<std::bidirectional_iterator I> void DoSomething(I& iter){..}
template<std::random_access_iterator I> void DoSomething(I& iter){..}

// Use a generic lambda to define a function template
auto Add = [](auto p1, auto p2){return p1+p2;} // unconstrained
std::integral auto Add = [](std::integral auto p1, std::integral auto p2){return p1+p2;} // constrained

// Constrained placeholders within for loop or as variable
for (std::integral auto x : vec){..}
std::integral auto myVar = 47;

Syntax for requires clause.

  • named concept
    requires std::integral<T>
  • a combination of named concepts
  • a requires expression
    template <unsigned int i>
    requires (i<=10)

    Recommendation: do not use such compile time predicates, prefer usage of named concepts!

Predefined concepts

  • std::regular
    behaves like an int, e.g. a reference Type int& is not regular.
    must support copy/default construction, assignment, equality, destruction, total ordering
  • std::copyable
    e.g. not fulfilled if no copy constructor exists
  • std::three_way_comparable
    ==,!=,<,>,<=,>=
  • std::same_as
  • std::derived_from
  • std::convertible_to
  • std::integral/signed_integral, unsigned_integral, floating_point
  • std::default_constructible
  • std::totally_ordered

Definig your own concept

template <typename T>
concept Arithmetic = std::is_integral<T>::value || std::is_floating_point<T>::value

// objects of type T can be added:
template <typename T>
concept Addable = requires (T a, T b) {a+b;}

// multiple requirements for type T 
template <typename T>
concept MyTypeRequirement = requires
{
    typename T::value_type; // type T has nested member value_type
    typename Other<T>; // template Other can be used with type T
}

For more infos see https://mariusbancila.ro/blog/2022/06/20/requires-expressions-and-requires-clauses-in-cpp20/