A concept is a named boolean predicate evaluated at compile time that constrains what types a template parameter will accept. Before C++20, templates were completely unconstrained — a function template would accept any type and only fail deep inside the instantiation with a cryptic multi-line error. Concepts fix both problems: they make the constraint part of the function’s signature (so it self-documents), and they catch type mismatches at the call site with a single, readable error line. For GPU and ML code, concepts are the right tool for kernel dispatch functions that only make sense for numeric types — you express “this template only works forDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/VrajPatel105/cpp-gpu-inference/llms.txt
Use this file to discover all available pages before exploring further.
int, float, or double” directly in the signature instead of relying on the reader to infer it from the body.
Function Templates: The Starting Point
A function template lets one function definition work for many types. The compiler generates a concrete version for each type it is called with.Print and IsEqual work for any type T. Print(5) instantiates Print<int>; Print(3.14) instantiates Print<double>. The compiler generates both at compile time.
Why Concepts Exist: The Unconstrained Template Problem
Unconstrained templates have two pain points:Poor error messages
If you call
add with a std::vector, the compiler tries to instantiate
the template and fails deep inside operator+. The error points to the
library internals, not your call site, and can span 50+ lines.Silent signatures
template <typename T> T add(T a, T b) tells you nothing about what T
must support. You have to read the body and infer the requirements — easy
to get wrong as the function grows.Built-In Concepts from <concepts>
The standard library provides ready-made concepts in <concepts>:
| Concept | Satisfied by |
|---|---|
std::integral<T> | int, char, short, long, long long, and their unsigned variants |
std::floating_point<T> | float, double, long double |
std::same_as<T, U> | T and U are the same type |
std::convertible_to<T, U> | T is implicitly convertible to U |
std::equality_comparable<T> | T supports == and != |
Custom Concepts: Numeric and Addable
The customConcepts.cpp file defines two custom concepts that demonstrate the two main forms of concept definition.
Anatomy of a Concept Definition
true for a given T if T satisfies std::integral or std::floating_point. Concepts compose with && and || exactly like boolean expressions.
The requires Expression
A requires expression checks that a set of operations on the type actually compile. If any expression inside the body is ill-formed for T, the concept evaluates to false.
Attaching Concepts to Templates
There are three equivalent syntaxes for applying a concept to a template parameter:- Abbreviated (cleanest)
- requires clause
- Inline requires
template <Numeric T>) is the most common in modern codebases.
Concepts in Practice: What the Compiler Error Looks Like
Uncommentingadd("hi", "bye") in customConcepts.cpp produces an error like:
In GPU/ML kernel dispatch code, concepts constrain template parameters to
numeric types so a generic
quantize<T> or dot_product<T> function cannot
accidentally be called with a string or a custom object. The constraint lives
in the signature — readable by anyone reviewing the API — and the compiler
enforces it without any runtime cost.