Understanding Clang's Sema Handling of C++20 Concepts
Learn how Clang’s Sema phase creates ConceptDecl nodes, evaluates constraint expressions, and produces precise diagnostics for C++20 concepts, plus practical verification steps and trade‑offs.
08 Sept 2025, 12:29 UTC

The problem: why concepts sometimes feel opaque during compilation
When you write a C++20 concept and use it in a template, the compiler may emit a cryptic "constraints not satisfied" error or, conversely, accept code that you expected to fail. Understanding what happens inside Clang’s semantic analysis (Sema) phase helps you diagnose these situations and decide whether a concept is the right tool for a given generic interface.
How Sema processes a concept definition
Clang’s front‑end parses a concept declaration and creates a ConceptDecl node in the AST. This node stores the concept’s name, its template parameter list, and the constraint expression that appears after the requires keyword. The constraint itself is represented as a ConstraintExpr tree, which later undergoes substitution when the concept is used in a template.
When a template (function, class, or variable) mentions the concept—either as a template parameter constraint or inside a requires-expression—Sema performs the following steps:
- Substitutes the template arguments into the concept’s parameter list.
- Instantiates the stored
ConstraintExprwith those arguments, producing a concrete boolean expression. - Evaluates that expression; if it yields
false, Sema emits a diagnostic that points to the location of the failing requirement.
Because the constraint expression is kept as an AST node, the diagnostic can highlight the exact sub‑expression that failed, rather than just the template instantiation site.
Worked example: diagnosing an unsatisfied concept
Consider the following code saved in example.cpp:
#include <type_traits>
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template<Addable T>
T sum(T a, T b) { return a + b; }
struct NoPlus {}; // does not provide operator+
int main() {
sum(NoPlus{}, NoPlus{}); // should fail
}
To see how Sema represents the concept, run:
clang -std=c++20 -fsyntax-only -Xclang -ast-dump example.cpp 2>&1 | grep -A2 -B2 "ConceptDecl\|ConstraintExpr"
You should observe output similar to (exact formatting may vary by Clang version):
ConceptDecl 0x55f... Addable
TemplateTypeParmDepth 0
RequiresExpr 0x55f... (the requires‑expression body)
...
ConstraintExpr 0x55f... (the substituted constraint when checking sum)
...
If you compile the program normally:
clang -std=c++20 example.cpp -o example
Clang will produce a diagnostic such as:
example.cpp:14:3: error: no matching function for call to 'sum'
sum(NoPlus{}, NoPlus{});
^~~
example.cpp:8:6: note: candidate template ignored: constraints not satisfied [with T = NoPlus]
T sum(T a, T b) { return a + b; }
^
example.cpp:5:19: note: because 'a + b' would be invalid: no viable overloaded '+ '
{ a + b } -> std::same_as<T>;
^
The note "constraints not satisfied" originates from Sema’s evaluation of the ConstraintExpr for the Addable concept after substituting T = NoPlus. The final line points directly to the requires‑expression that caused the failure.
Trade‑offs and limitations
While concepts improve interface clarity, they introduce compile‑time overhead. Each time a template is instantiated, Sema may re‑evaluate the associated constraint expression. For deeply nested requires‑expressions or concepts that themselves depend on other concepts, this can lead to repeated constraint checking and noticeable increases in build time, especially in large code bases.
Additionally, full concept diagnostics require Clang 10 or later. Earlier versions lack some of the constraint‑refinement improvements and may produce less precise error messages or miss certain satisfaction checks.
To verify that your Clang version provides the expected support, run:
clang --version
Ensure the output shows clang version X.Y.Z with X ≥ 10. If you are using an older compiler, consider upgrading or limiting concept usage to simple cases.
Actionable closing
When designing generic interfaces, start with a clear, minimal concept that captures the essential operation. Use -fsyntax-only -Xclang -ast-dump to inspect the generated ConceptDecl and ConstraintExpr nodes if you suspect a concept is not behaving as expected. Keep an eye on compile‑time metrics; if builds become noticeably slower, profile the template instantiation depth and consider simplifying the constraint or moving some checks to static_assert inside the template body.
By understanding how Sema treats concepts—from AST creation to constraint evaluation—you can write more predictable generic code and diagnose failures faster.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.