Concepts
C++20 Concepts
Section titled “C++20 Concepts”Concepts specify constraints on template parameters, enabling better error messages and cleaner template code.
Introduction to Concepts
Section titled “Introduction to Concepts”Concepts were introduced in C++20 to address long-standing issues with template metaprogramming. They allow you to specify what a template parameter must be capable of doing, rather than just accepting any type.
Built-in Concepts
Section titled “Built-in Concepts”The standard library provides many useful concepts in the <concepts> header:
#include <concepts>#include <type_traits>
// Common conceptstemplate<typename T> concept Integral = std::integral<T>;template<typename T> concept FloatingPoint = std::floating_point<T>;template<typename T> concept Signed = std::signed_integral<T>;template<typename T> concept Unsigned = std::unsigned_integral<T>;template<typename T> concept Copyable = std::copyable<T>;template<typename T> concept Movable = std::movable<T>;template<typename T> concept DefaultConstructible = std::default_initializable<T>;template<typename T> concept Hashable = requires(T a) { { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>; };Defining Custom Concepts
Section titled “Defining Custom Concepts”Using requires Expression
Section titled “Using requires Expression”#include <concepts>#include <iostream>
// Define a concept that checks if a type supports additiontemplate<typename T>concept Addable = requires(T a, T b) { a + b; // Must support + operator};
// Define a concept with return type constrainttemplate<typename T>concept Numeric = requires(T a, T b) { { a + b } -> std::convertible_to<T>; { a - b } -> std::convertible_to<T>; { a * b } -> std::convertible_to<T>; { a / b } -> std::convertible_to<T>;};
// Using the concepttemplate<Addable T>T add(T a, T b) { return a + b;}
int main() { std::cout << add(1, 2) << std::endl; // 3 std::cout << add(1.5, 2.5) << std::endl; // 4.0 // add("hello", "world"); // Error: no matching operator+}Using require Clause in Templates
Section titled “Using require Clause in Templates”#include <concepts>#include <vector>
// Method 1: Template constrainttemplate<std::integral T>T factorial(T n) { if (n <= 1) return T(1); return n * factorial(n - 1);}
// Method 2: Short form with constrained autotemplate<std::integral T>T factorial(T n) { T result = 1; for (T i = 2; i <= n; i++) { result *= i; } return result;}
// Method 3: requires clausetemplate<typename T> requires std::integral<T>T factorial(T n) { if (n <= 1) return T(1); return n * factorial(n - 1);}Constrained Auto (C++20)
Section titled “Constrained Auto (C++20)”#include <concepts>#include <iostream>
// Constrained polymorphic variablestd::integral auto i = 42;std::floating_point auto f = 3.14;
void process(std::integral auto value) { std::cout << "Integer: " << value << std::endl;}
void process(std::floating_point auto value) { std::cout << "Float: " << value << std::endl;}
int main() { process(10); // Calls integer version process(3.14); // Calls floating version}Combining Concepts
Section titled “Combining Concepts”#include <concepts>
// And combinationtemplate<typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
// More complex concepttemplate<typename T>concept Comparable = requires(T a, T b) { { a < b } -> std::convertible_to<bool>; { a > b } -> std::convertible_to<bool>; { a == b } -> std::convertible_to<bool>;};
// Using requires with multiple conditionstemplate<typename T>concept Hashable = requires(T value) { { std::hash<T>{}(value) } -> std::convertible_to<std::size_t>;} && requires(T a, T b) { { a == b } -> std::convertible_to<bool>; { a != b } -> std::convertible_to<bool>;};Concepts with Multiple Parameters
Section titled “Concepts with Multiple Parameters”#include <concepts>#include <iostream>
// Concept with multiple parameterstemplate<typename T, typename U>concept CommonArithmetic = std::integral<T> && std::integral<U> || std::floating_point<T> && std::floating_point<U>;
// Or use requirestemplate<typename T, typename U> requires std::integral<T> && std::integral<U>auto add(T a, U b) { return a + b;}
int main() { std::cout << add(5, 10) << std::endl; // 15 std::cout << add(5L, 10) << std::endl; // 15}Standard Library Concepts
Section titled “Standard Library Concepts”The C++20 standard library includes many useful concepts:
#include <concepts>#include <type_traits>
// Core language conceptstemplate<typename T> concept same_as<T, U>;template<typename T, typename U> concept convertible_to<T, U>;template<typename T> concept movable<T>;template<typename T> concept copyable<T>;template<typename T> concept semiregular<T>;template<typename T> concept regular<T>;
// Comparison conceptstemplate<typename T> concept equality_comparable<T>;template<typename T> concept totally_ordered<T>;
// Object conceptstemplate<typename T> concept default_initializable<T>;template<typename T> concept move_constructible<T>;template<typename T> concept copy_constructible<T>;
// Callable conceptstemplate<typename F, typename... Args> concept invocable<F, Args...>;template<typename F, typename... Args> concept regular_invocable<F, Args...>;Benefits of Concepts
Section titled “Benefits of Concepts”-
Better Error Messages: Instead of cryptic template errors, you get clear messages about what constraint was violated
-
Self-Documenting Code: Concepts make template requirements explicit
-
Faster Compilation: Compiler can reject invalid instantiations earlier
-
IDE Support: Better IntelliSense and autocomplete
Example: Complete Concept Usage
Section titled “Example: Complete Concept Usage”#include <concepts>#include <iostream>#include <vector>#include <string>
// Custom conceptstemplate<typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>;};
template<typename T>concept Printable = requires(std::ostream& os, T value) { { os << value } -> std::convertible_to<std::ostream&>;};
// Function templates using conceptstemplate<Numeric T>T square(T value) { return value * value;}
template<Addable T>T sum(T a, T b) { return a + b;}
template<Printable T>void print(const T& value) { std::cout << value << std::endl;}
int main() { // Works with integers std::cout << square(5) << std::endl; // 25 std::cout << sum(10, 20) << std::endl; // 30
// Works with doubles std::cout << square(3.5) << std::endl; // 12.25 std::cout << sum(1.5, 2.5) << std::endl; // 4.0
// Works with strings print("Hello, World!");
// This would fail at compile time: // square("hello"); // Error: not Numeric // sum(1, "world"); // Error: not Addable
return 0;}Best Practices
Section titled “Best Practices”- Use standard library concepts when possible
- Keep concepts simple and focused
- Prefer requires expression syntax
- Document complex constraints
- Test concept constraints with static_assert
// Test concepts at compile timestatic_assert(std::integral<int>);static_assert(std::floating_point<double>);static_assert(Numeric<int>);static_assert(Addable<std::string>);Key Takeaways
Section titled “Key Takeaways”- Concepts specify constraints on template parameters
- Use
requiresexpression to define custom concepts - Prefer standard library concepts when available
- Concepts improve error messages and code readability
- Constrained auto (C++20) simplifies template code
Basic Concept
Section titled “Basic Concept”#include <concepts>
template<typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>T add(T a, T b) { return a + b;}Defining Custom Concepts
Section titled “Defining Custom Concepts”template<typename T>concept Addable = requires(T a, T b) { a + b; // Must support + operator};
template<Addable T>T doubleValue(T value) { return value + value;}Using Concepts
Section titled “Using Concepts”template<typename T> requires std::integral<T>T factorial(T n) { if (n <= 1) return 1; return n * factorial(n - 1);}
// Short formtemplate<std::integral T>T factorial(T n) {}
// Constrained autostd::integral auto x = 42;Benefits
Section titled “Benefits”- Better error messages
- Self-documenting templates
- Faster compilation