Writing safer C++ code with concepts


Concepts in C++20 provide a clean way to express what types your templates expect. This makes template code more readable and helps catch type errors at compile time, rather than during debugging.

#include <concepts>
#include <iostream>

template<typename T>
concept Printable = requires(T t) {
    std::cout << t;
};

template<Printable T>
void print(const T& value) {
    std::cout << value << "\n";
}


In this example, Printable is a concept that checks if a type can be used with std::cout. The print function can only be used with types that satisfy this condition.


Why use concepts:

  • To define clear constraints on templates
  • To make error messages easier to understand
  • To avoid complicated workarounds with enable_if

When to use:

  • When writing generic functions or classes
  • When you want to enforce that types meet specific requirements
  • When you want to document assumptions about types directly in the code

Combining multiple requirements

We can use logical operators inside concepts:

template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;

template<Number T>
T add(T a, T b) {
    return a + b;
}


Here, add works for both int and double, but not for std::string or std::vector.

 We can also write our own concepts

We are not limited to standard ones like std::integral. We can define our own for interfaces we care about:

template<typename T>
concept has_size = requires(T t) {
    { t.size() } -> std::convertible_to<std::size_t>;
};


This allows us to constrain functions to work only with containers or classes that define a .size() method returning a number.


 The common pitfall is that concepts DO NOT check semantics

They check whether code compiles, not whether it does what you intended.

template<typename T>
concept comparable = requires(T a, T b) {
    { a < b } -> std::convertible_to<bool>;
};


This concept will accept a type that defines operator<, but it does not check whether that operator gives meaningful or consistent results.

Summary

Concepts help express intent, restrict misuse, and simplify template error messages. I use them when:

  • I write generic code that should only work for certain kinds of types
  • I want to prevent misuse of APIs
  • I care about clearer compile-time diagnostics

But be aware of over-constraining or assuming concepts validate behavior, not just type compatibility.

Leave a Reply

Your email address will not be published. Required fields are marked *