A C++20 type-constraint (e.g. 'Numeric T') used in place of 'typename' /
'class' in a template parameter list is now recognised as the standard
shorthand for 'typename T' plus a 'requires Concept<T>' clause. The
parm fallback in 'templateparameter' classifies the parsed parm via
classify_template_param_type():
- resolves to a concept in scope -> rewrite to 'typename T' (or
'v.typename Ts...' for a variadic pack) and attach a 'concept-id'
constraint atom on the parm's "constraint" attribute, matching the
parm representation promote_abbreviated_template() already builds
for 'Concept auto x';
- resolves to a typedef, class or enum, or names a primitive -> leave
the parm as a non-type template parameter;
- is an unqualified identifier not declared anywhere SWIG has seen ->
issue an error so the user knows to make the concept visible.
The classifier is built on existing SwigType helpers - SwigType_type
covers primitives (including multi word forms like 'unsigned int' /
'long long' and resolvable typedefs such as 'size_t'); SwigType_isenum,
SwigType_issimple, SwigType_istemplate and SwigType_isvariadic /
SwigType_del_variadic handle decoration, enum and template-id forms.
template<Numeric T> T cube(T x);
template<nest::Integral T> T half(T x); // ::-qualified
template<Numeric T, typename U> T scale(T x, U factor); // mixed
template<Numeric T = int> T identity(T x); // default arg
template<Numeric... Ts> int count_numeric(Ts...); // variadic
template<typename X, Numeric... Ts> int tag_count(X, Ts...);
template<SmallNumeric X, Numeric... Ts> int small_tag_count(X, Ts...);
template<Numeric T> class Box { T v; }; // class template
A type-constraint that itself takes template arguments before the
parameter name (e.g. 'template<std::convertible_to<int> T>') is not yet
parsed and must still be moved to a 'requires'-clause.
Includes new errors/cpp_concept_not_visible test exercising the
"undeclared concept" error path, plus a non-template-member-with-
requires-clause case in cpp20_concepts_extra exercising
ConstrainedHolder<T>::cube() const requires Numeric<T> (no parser
change needed).
Assisted-by: Claude Code (Opus 4.7)
Add testcases for concept constrained overload by arity, member operator
overloads, structural partial specialization with a requires-clause, and
the concept only function template redefinition that trips warning 302
(errors/cpp_concept_redefinition). Document existing support for
constrained class templates and member function templates of plain
classes, and the limitations around constraint subsumption: same-
signature templates differing only by requires-clause are dropped, and
concept only "partial specializations" are not selected.
Assisted-by: Claude Code (Opus 4.7)
cpp_template_decl's mid rule action no longer string concatenates the
prefix and trailing requires-clauses with ' && '. Instead, it finds
any existing trailing constraint subtree on the inner cdecl/class
node, removes it from the child chain, and conjoins it with the
prefix subtree via Constraint_combine("and", prefix, trailing). The
result is a single op="and" constraint subtree (or the bare prefix
when no trailing is present), and the rendered "requires" string is
regenerated from that subtree via Constraint_str.
For 'both_clauses' from cpp20_concepts_extra.i
template<typename T>
requires Numeric<T>
T both_clauses(T x) requires Sized<T> { return x + x; }
the cdecl now carries a single constraint subtree:
+++ constraint op="and"
+++ constraint op="atom" kind="concept-id" name="Numeric<(T)>"
+++ constraint op="atom" kind="concept-id" name="Sized<(T)>"
with the flat 'requires' string "Numeric<(T)> && Sized<(T)>" derived
from it. After %template(both_clauses_int) both_clauses<int>; the
two atoms substitute to "Numeric<(int)>" and "Sized<(int)>" via the
structural walker added in commit b38b45cb6.
Per [temp.constr.decl]/3, the C++20 standard conjoins the prefix and
trailing requires-clauses via '&&'; the structural form preserves
that semantic exactly while producing a more useful tree for any
future consumer that wants to walk the conjunction operands.
A new both_clauses test case in cpp20_concepts_extra exercises the
combine path; the runtime tests confirm the wrapped function returns
the expected result.
Assisted-by: Claude Code (Opus 4.7)
A new cpp20_concepts_extra.i test case covers C++20 constraint primary
forms not previously exercised by the suite:
- identity_non_numeric<T> '(!Numeric<T>)' - negation only legal
when wrapped in parens
- mix_add<T,U> multi parameter requires-expression
binding two unrelated template parameters
- AllNumeric<Ts...> variadic concept defined by a fold-
expression over '&&'
- sum_all<T,Rest...> variadic function template constrained
by the variadic concept
- trait_primary<T> 'std::is_integral_v<T>' as a constraint
atom (non-concept-id boolean primary)
- deeper<T> deeper nesting of '&&' / '||' across
multiple parens levels
cpp20_concepts_classes.i gains an OutOfLineBox<T> case: the member
function template scaled<U> is declared in class with
'requires Numeric<U>' and defined out of line with the same prefix
requires-clause on its own template head, exercising the
requires_clause_opt path on a doubly templated declaration.
Matching Python and Java runme files validate the wrapped behaviour;
both new test cases are registered in Examples/test suite/common.mk
under CPP20_TEST_CASES.
No SWIG source changes are needed - the existing scanner level
constraint text capture handles every form added here. The tests
serve as a baseline before the planned structural rework of the
constraint representation.
Assisted-by: Claude Code (Opus 4.7)