Skip to main content

Documentation 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.

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 for 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.
#include <iostream>
#include <concepts>
using namespace std;

template<typename T>
void Print(T value){
    cout << "The value is : " << value << endl;
}

template <typename T>
bool IsEqual(T a, T b){
    return a == b;
}

int main(){
    Print(5);
    cout << "1==1:" << boolalpha << IsEqual(1, 1) << endl;
    return 0;
}
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.
Concepts address both: constraints appear in the signature and violations are reported at the call site with one clean error.

Built-In Concepts from <concepts>

The standard library provides ready-made concepts in <concepts>:
ConceptSatisfied 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.
#include <iostream>
#include <concepts>

// Concept defined as a logical combination of existing concepts
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

// Concept defined with a requires expression — checks that the operation compiles
template <typename T>
concept Addable = requires(T a, T b) { a + b; };

// Template constrained by Numeric
template <Numeric T>
T add(T a, T b) { return a + b; }

int main() {
    std::cout << add(3, 4)     << "\n";   // works — int satisfies Numeric
    std::cout << add(2.5, 1.5) << "\n";   // works — double satisfies Numeric
    // std::cout << add("hi", "bye");      // compile error: const char* is not Numeric
    return 0;
}

Anatomy of a Concept Definition

template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
//       ^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//       name     boolean expression evaluated at compile time
The concept evaluates to 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.
template <typename T>
concept Addable = requires(T a, T b) {
    a + b;   // T must support operator+
};
You can combine both forms to build more specific constraints:
template <typename T>
concept Printable = Numeric<T> && requires(T x) { std::cout << x; };
//                  ^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//                  must be      AND must be streamable to cout
//                  Numeric

Attaching Concepts to Templates

There are three equivalent syntaxes for applying a concept to a template parameter:
template <Numeric T>
T add(T a, T b) { return a + b; }
All three are equivalent. The abbreviated form (template <Numeric T>) is the most common in modern codebases.

Concepts in Practice: What the Compiler Error Looks Like

Uncommenting add("hi", "bye") in customConcepts.cpp produces an error like:
error: no matching function for call to 'add'
note: candidate template ignored: constraints not satisfied
note: because 'const char *' does not satisfy 'Numeric'
note: 'std::integral<const char *>' evaluated to false
note: 'std::floating_point<const char *>' evaluated to false
The error is at the call site, not inside the template body. The constraint violation is named and traced back to exactly which sub-concept failed.
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.

Build docs developers (and LLMs) love