blob: 2c975c14d4aabfca79c56c46097302b8cca638d1 [file]
%module cpp20_concepts_extra
// Additional C++20 concept and requires-clause forms beyond the core suite:
// negation via parens, multi parameter requires-expression with mixed types,
// fold-expression over a pack (variadic concept), type trait primary, deeper
// nesting of '&&' / '||' inside parens, and concept bodies that are ordinary
// expressions.
%inline %{
#include <concepts>
#include <type_traits>
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<typename T>
concept Sized = (sizeof(T) <= 8);
// Negation of a concept-id is not a primary-expression in C++20; the user
// must wrap it in parens. A struct that is not Numeric satisfies the
// constraint, so the function instantiates only for non-numeric types.
struct Tag {
int n;
Tag(int v) : n(v) {}
int value() const { return n; }
};
template<typename T>
T identity_non_numeric(T x) requires (!Numeric<T>) { return x; }
// Multi parameter requires-expression mixing two unrelated template
// parameters, exercising the requirement-parameter-list with more than one
// parm of differing type.
template<typename T, typename U>
T mix_add(T a, U b) requires requires(T t, U u) { t + u; } {
return a + (T)b;
}
// Variadic concept defined by a fold-expression over '&&' on a pack.
template<typename... Ts>
concept AllNumeric = (Numeric<Ts> && ...);
// Variadic function template constrained by the variadic concept. The base
// case is unconstrained; the recursive case adds the constraint.
template<typename T>
T sum_all(T t) { return t; }
template<typename T, typename... Rest>
T sum_all(T first, Rest... rest) requires AllNumeric<T, Rest...> {
return first + sum_all<T>((T)rest...);
}
// Type trait primary - 'std::is_integral_v<T>' is a constexpr bool, used
// here as a constraint atom rather than a concept-id.
template<typename T>
T trait_primary(T x) requires std::is_integral_v<T> { return x + x; }
// Deeper nesting of '&&' and '||' inside parens.
template<typename T>
T deeper(T x) requires (Numeric<T> && (Sized<T> || std::is_pointer_v<T>)) {
return x;
}
// Both prefix AND trailing requires-clauses on the same function template.
// Per [temp.constr.decl]/3 the C++20 standard conjoins the two; the parser
// builds a single op="and" constraint subtree from the two clauses.
template<typename T>
requires Numeric<T>
T both_clauses(T x) requires Sized<T> {
return x + x;
}
// Default template argument paired with a requires-clause - exercises the default argument plumbing in
// cpp_template_decl together with the prefix requires-clause.
template<typename T = int> requires Numeric<T>
T identity_default(T x) { return x; }
// Concept refinement - 'Integer' is defined in terms of 'Numeric', so the captured requires text for
// 'requires Integer<T>' contains a nested concept name. C++20 subsumption (where the more refined
// concept wins between two same signature overloads) is not modelled by SWIG - that broken path is
// covered by errors/cpp_concept_redefinition.i. Here only the positive parse and instantiate path is
// exercised.
template<typename T>
concept Integer = Numeric<T> && std::integral<T>;
template<typename T> requires Integer<T>
T succ(T x) { return x + 1; }
// Trailing requires-clause on a non-template member of a class template - the
// method itself has no template head of its own; the constraint refers only to
// the enclosing class's template parameter.
template<typename T>
struct ConstrainedHolder {
T value;
ConstrainedHolder(T v) : value(v) {}
T cube() const requires Numeric<T> { return value * value * value; }
};
// Subsumption driven overload selection (negative case, kept here as documentation of what does not
// work). In C++20 the Integer constrained overload would win for integral types because Integer
// subsumes Numeric. SWIG sees the two same signature templates as a redefinition (warning 302) and
// drops the second declaration, so subsumption based dispatch cannot be expressed:
//
// template<typename T> requires Numeric<T> T pick(T x) { return x; }
// template<typename T> requires Integer<T> T pick(T x) { return x + 1; } // dropped, warning 302
// A constraint-expression is any logical-or-expression, not only a chain of concept-ids: a bare
// boolean literal and a comparison with a top level '>' are both concept bodies.
template<typename T>
concept Anything = true;
template<typename T>
concept NonEmpty = sizeof(T) > 0;
template<typename T>
T pass_through(T x) requires Anything<T> && NonEmpty<T> { return x; }
// A literal is a primary-expression, so a boolean literal is a constraint on its own.
template<typename T>
T literal_constraint(T x) requires true { return x; }
// GCC accepts a virt-specifier after the trailing requires-clause, in both the plain and the trailing return
// type spellings of the member. Both virt-specifiers are covered, in both orderings, and with and
// without the virtual keyword the declaration may repeat. Standard C++ does not allow a trailing requires-clause on a
// virtual function, so other compilers see the members without one.
#if defined(SWIG) || (defined(__GNUC__) && !defined(__clang__))
#define REQUIRES_NUMERIC(T) requires Numeric<T>
#else
#define REQUIRES_NUMERIC(T)
#endif
template<typename T>
struct VirtBase {
virtual T plain(T x) { return x; }
virtual T arrow(T x) { return x; }
virtual T over(T x) { return x; }
virtual T over_arrow(T x) { return x; }
virtual T both(T x) { return x; }
virtual T both_reversed(T x) { return x; }
virtual ~VirtBase() {}
};
template<typename T>
struct VirtDerived : VirtBase<T> {
T plain(T x) REQUIRES_NUMERIC(T) final { return x + 1; }
auto arrow(T x) -> T REQUIRES_NUMERIC(T) final { return x + 2; }
virtual T over(T x) REQUIRES_NUMERIC(T) override { return x + 3; }
virtual auto over_arrow(T x) -> T REQUIRES_NUMERIC(T) override { return x + 4; }
T both(T x) REQUIRES_NUMERIC(T) final override { return x + 5; }
auto both_reversed(T x) -> T REQUIRES_NUMERIC(T) override final { return x + 6; }
};
// A type-constraint on an 'auto' variable placeholder, in each of the three initialiser forms, and on 'decltype(auto)'. The
// constraint is kept on the variable and the type is deduced from the initialiser as for a plain 'auto'.
Numeric auto constrained_var = 42;
Numeric auto constrained_braced_var{1.5};
Numeric auto constrained_multi1 = 7, constrained_multi2 = 8;
Numeric decltype(auto) constrained_decltype_auto_var = constrained_var;
%}
%template(identity_non_numeric_tag) identity_non_numeric<Tag>;
%template(mix_add_id) mix_add<int, double>;
%template(sum_all_iii) sum_all<int, int, int>;
%template(sum_all_ddd) sum_all<double, double, double>;
%template(trait_primary_int) trait_primary<int>;
%template(deeper_int) deeper<int>;
%template(both_clauses_int) both_clauses<int>;
%template(identity_default_int) identity_default<int>;
%template(identity_default_double) identity_default<double>;
%template(succ_int) succ<int>;
%template(ConstrainedHolderInt) ConstrainedHolder<int>;
%template(pass_through_int) pass_through<int>;
%template(literal_constraint_int) literal_constraint<int>;
%template(VirtBaseInt) VirtBase<int>;
%template(VirtDerivedInt) VirtDerived<int>;