This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these messages)
|
Concepts are an extension to the templates feature provided by the C++ programming language.[1] Concepts are named Boolean predicates on template parameters, evaluated at compile time. A concept may be associated with a template (class template, function template, member function of a class template, variable template, or alias template), in which case it serves as a constraint: it limits the set of arguments that are accepted as template parameters.
Originally dating back to suggestions for C++11, the original concepts specification has been revised multiple times before formally being standardised in C++20.
Main uses
editThe main uses of concepts are:
- introducing type-checking to template programming
- simplified compiler diagnostics for failed template instantiations
- selecting function template overloads and class template specializations based on type properties
- constraining automatic type deduction
Traditionally, template type restrictions were expressed using substitution failure is not an error (SFINAE), but concepts allow for a clearer expression of restrictions.
Constraint types and usage
editThere are five different places in a function template signature where a constraint can be used (labeled below from 1 through 5):[2]
template <Concept1 T>
requires Concept2<T>
Concept3 auto myFunction(Concept4 auto param) requires Concept5<T>;
Concept1: A type-constraint. This kind replacesclassor typename for declaring a template type parameter. When using a concept instead of the former two the type is constraint.Concept2: A requires-clause. Whenever a type-constraint does not work, for example, because the concept takes multiple parameters, a requires-clause can be used to apply more elaborated constraints.Concept3,Concept4: A constrained placeholder type. The same syntax is available for placeholder variable aka.autovariable. C++20 added abbreviated function templates which useautoas a placeholder type in the parameter declaration.[3] A constrained placeholder type allows to put constraints on the automatically deduced return type of a function or a variable.Concept5: A trailing requires-clause. This form is similar toConcept2with one notable exception. A trailing requires-clause can be applied to a function in a class template. This allows the function to remain a regular, template-free function, which can be enabled or disabled depending on the functions trailing requires-clause.
The constraint forms Concept1 and Concept2 can be used in all kinds of templates.
Examples
editInheritance constraint
editThe following demonstrates using a concept as an upper bound for inheritance constraints on types, by creating a concept ExtendsPlayer satisfied only by classes which inherit from a base class Player, blocking any type that does not.
import std;
using std::is_base_of_v;
using std::vector;
class Player {
// ...
};
template <typename T>
concept ExtendsPlayer = is_base_of_v<Player, T>;
// T is required to be a type whose inheritance upper bound is Player,
// blocking any type that does not inherit from Player
template <ExtendsPlayer T>
void processListOfPlayers(const vector<T>& players) {
// ...
}
This is similar to constrained generics in Java, and is equivalent to the following example:
import java.util.List;
class Player {
// ...
}
public class Example {
// T is constrained to types that inherit from Player
public static <T extends Player> void processListOfPlayers(List<T> players) {
// ...
}
}
equality_comparable
editThe following is a possible definition of the concept std::equality_comparable from the <concepts> header of the C++ Standard Library. This concept is satisfied by any type T such that for lvalues a and b of type T, the expressions a == b and a != b as well as the reverse b == a and b != a compile, and their results are convertible to a type that satisfies the concept "boolean-testable":
/**
* @namespace std
* @brief The C++ Standard Library namespace
*/
namespace std {
/**
* @internal
* @concept _WeaklyEqualityComparableWith
* @brief The following concept is an implementation detail used to build equality_comparable
* @tparam T the first template parameter to compare
* @tparam U the second template parameter to compare
*/
template <typename T, typename U>
concept _WeaklyEqualityComparableWith = requires (const remove_reference<T>& a, const remove_reference<U>& b) {
{ a == b } -> same_as<bool>;
{ a != b } -> same_as<bool>;
{ b == a } -> same_as<bool>;
{ b != a } -> same_as<bool>;
};
/**
* @concept equality_comparable
* @brief Checks that a == b, a != b, b == a, b != a compile and convert to bool
* @tparam T the type to compare
*/
template <typename T>
concept equality_comparable = _WeaklyEqualityComparableWith<T, T>;
}
A function template constrained on this concept may be declared as follows:
// constrained abbreviated function template declaration
// using a constrained placeholder type (Concept4 from above)
void f(const equality_comparable auto& x);
or
// constrained function template declaration
// using a type constraint (Concept1 from above)
template <equality_comparable T>
void f(const T& x);
And may be called as usual:
// OK, int satisfies equality_comparable
f(42);
Type-safe variadic parameters
editC++ lacks type-safe variadic functions, like those of Java: void f(String... args), having only C-style <cstdarg>, which lacks type safety. However, C++ does feature variadic templates, allowing for an indefinite number of template parameters.
To constrain the parameter to be only of type T (similar to T... args) in Java, one can use the concept std::same_as[4] or std::convertible_to (for types which may be intended to be convertible).[5]
using std::same_as;
// Equivalent to Java declaration
// <T> void fn(T... args)
template <typename T>
void fn(same_as<T> auto... args) {
// ...
}
This can be further constrained to any specific type:
using std::convertible_to;
using std::same_as;
using std::string;
// Equivalent to Java declaration
// void foo(int... args)
void foo(same_as<int> auto... args) {
// ...
}
// Equivalent to Java declaration
// void bar(String... args)
void bar(convertible_to<string> auto... args) {
// ...
}
Interfaces
editIn object-oriented programming, an interface defines a set of method signatures which the class must implement in order to satisfy it. A concept may be seen as similar to an interface, which can define that a type satisfies various requirements.
import std;
using std::same_as;
using std::floating_point;
template <typename T>
concept Drawable = requires (T t) {
{ t.draw() } -> same_as<void>; // T::draw must return void
{ t.area() } -> floating_point; // matches any floating-point type
};
// The class Circle satisfies the Drawable concept
// Circle does not need to declare it satisfies Drawable, however
class Circle {
private:
const double radius;
public:
explicit Circle(double r):
radius{r} {}
void draw() const {
// draw a circle...
}
double area() const noexcept {
return std::numbers::pi * radius * radius;
}
};
// Only types which satisfy Drawable may be used
template <Drawable T>
void render(const T& shape) {
// ...
shape.draw();
std::println("Drew shape with area {}", shape.area());
}
This would be roughly similar to the following in Java:
interface Drawable {
void draw(); // must return void
double area(); // must return double
}
// Circle must explicitly declare that it implements Drawable
class Circle implements Drawable {
private final double radius;
public Circle(double r) {
this.radius = r;
}
@Override
public void draw() {
// draw a circle...
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// Only types which implement Drawable may be used
static void render(Drawable shape) {
// ...
shape.draw();
System.out.printf("Drew shape with area %f", shape.area());
}
C++ concepts resemble Go interfaces very closely. Unlike Java interfaces, Go interfaces do not require a type to declare that it implements the interface, as types implicitly satisfy the interface as long as it implements all of its requirements.[6] For example, this Drawable concept in C++ is roughly equivalent to the Go interface:
import "math"
type Drawable interface {
Draw()
Area() float64
}
type Circle struct {
radius float64
}
func (c Circle) Draw() {
// draw a circle...
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
Compiler diagnostics
editIf a programmer attempts to use a template argument that does not satisfy the requirements of the template, the compiler will generate an error. When concepts are not used, such errors are often difficult to understand because the error is not reported in the context of the call, but rather in an internal, often deeply nested, implementation context where the type was used.
For example, std::sort requires that its first two arguments be random-access iterators. If an argument is not an iterator, or is an iterator of a different category, an error will occur when std::sort attempts to use its parameters as bidirectional iterators. Consider std::list<T>, which implements a doubly-linked list, and is not random-access:
using std::list;
// std::list is typically a doubly-linked list, whose iterators are not random-access
list<int> l = {2, 1, 3};
std::sort(l.begin(), l.end());
Typical compiler diagnostic without concepts is over 50 lines of output, beginning with a failure to compile an expression that attempts to subtract two iterators:
In instantiation of 'void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = std::_List_iterator<int>; _Compare = __gnu_cxx::__ops::_Iter_less_iter]': error: no match for 'operator-' (operand types are 'std::_List_iterator<int>' and 'std::_List_iterator<int>') std::__lg(__last - __first) * 2, [..]
If concepts are used, the error can be detected and reported in the context of the call:
error: cannot call function 'void std::sort(_RAIter, _RAIter) [with _RAIter = std::_List_iterator<int>]' note: concept 'RandomAccessIterator()' was not satisfied
Overload resolution
editConcepts can be used to choose function template overloads and class template specializations based on properties of their template arguments, as an alternative to SFINAE and tag dispatching. If an argument satisfies more than one concept, the overload associated with the more constrained concept is chosen.
Type deduction
editConcepts may be used instead of the unconstrained type deduction placeholder auto in variable declarations and function return types:
auto x1 = f(y); // the type of x1 is deduced to whatever f returns
Sortable auto x2 = f(y); // the type of x2 is deduced, but only compiles if it satisfies Sortable
Implementation status
editHistory
editA different form of Concepts, popularly known as "C++0x Concepts"[11], was temporarily accepted into the working paper for C++11 but was removed in 2009.[12] In addition to concepts themselves, "C++0x Concepts" included concept maps (a feature that could make it possible, for example, for the concept Stack to accept std::vector, automatically mapping Stack operations such as push() to differently named operations on std::vector, such as push_back()) and axioms (a facility to specify semantic properties such as associativity or commutativity, allowing the compiler to take advantage of these properties without proof). The proposed syntax looked something like the following:
template <ValueType T>
concept_map ForwardIterator<T*> {
typedef T value_type;
};
concept TotalOrder<typename Operator, typename T> {
axiom Transitivity(Operator op, T x, T y, T z) {
if (op(x, y) && op(y, z)) {
op(x, z) <=> true;
}
}
}
The original proposal was ultimately abandoned due to resolution ambiguity in compilation as well as increased complexity in compile-time behavior, while axioms could not be verified during compilation. In contrast to this abandoned proposal, the C++20 version of Concepts is sometimes referred to as "Concepts Lite".[13]
During the C++ standards committee meeting in March 2016, the evolution working group moved to merge Concepts into the mainline C++17 standard, but the motion was defeated in full committee.[14]
Concepts v1 was merged into the C++20 draft.[15]
"The One Range" version of Range feature that depend on concepts was also merged into C++20.
Type constraints in other languages
editIn C#, a generic type constraint is expressed with a where clause, which can be as expressive as concepts but are not named.[16]
using System;
public class MyGenericClass<T, U>
where T : IComparable<T>, allows ref struct
where U : class, notnull, new()
{
// ...
}
Java has wildcard generics, which are not as expressive as concepts but can represent bounds on types.[17]
import java.util.List;
import java.util.stream.Collectors;
public static <T extends CharSequence & Comparable<T>> List<String> copyWhenGreater(List<T> list, T threshold) {
return list.stream()
.filter(item -> item.compareTo(threshold) > 0)
.map(T::toString)
.collect(Collectors.toList());
}
Kotlin, does not support Java-style type wildcards. However, it represents ? instead represented as * (for example, List<*>). It otherwise has C#-style where clauses:[18]
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence, T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
Rust also uses where clauses to bound traits.
use std::cmp::Ord;
struct MyStruct<T>
where
T: Ord + Default,
{
value: T,
}
impl<T> MyStruct<T>
where
T: Ord + Default,
{
fn new(value: T) -> Self {
MyStruct { value }
}
fn is_less_than(&self, other: &Self) -> bool {
self.value < other.value
}
}
See also
editNotes
edit- ↑ cppreference.com (6 June 2026). "Constraints and concepts". cppreference.com. cppreference.com.
- ↑ Fertig, Andreas (2021). Programming with C++20. Fertig Publications. p. 23. ISBN 978-3-949323-01-0.
- ↑ "ISO/IEC 14882:2020". ISO. December 2020. Retrieved 14 July 2022.
- ↑ cppreference.com. "std::same_as". cppreference.com. cppreference.com. Retrieved 17 May 2026.
- ↑ cppreference.com. "std::convertible_to". cppreference.com. cppreference.com. Retrieved 17 May 2026.
- ↑ The Go Authors. "A tour of Go - Interfaces". go.dev. The Go Authors. Retrieved 5 May 2026.
- ↑ "GCC 6 Release Series - Changes, New Features, and Fixes".
- ↑ "C++ compiler support (gcc)".
- ↑ "C++ compiler support".
- ↑ "C++ Support in Clang".
- ↑ ISO C++. "C++0x Concepts - Historical FAQs". isocpp.org. ISO C++. Retrieved 27 February 2026.
- ↑ Bjarne Stroustrup (22 July 2009). "The C++0x "Remove Concepts" Decision". Dr. Dobbs. Archived from the original on 9 October 2012.
- ↑ Andrew Sutton (24 February 2013). "Concepts Lite: Constraining Templates with Predicates". isocpp.org.
- ↑ Honermann, Tom (6 March 2016). "Why Concepts didn't make C++17". honermann.net. Archived from the original on 2 October 2018. Retrieved 19 April 2016.
- ↑ "2017 Toronto ISO C++ Committee Discussion Thread (Concepts in C++20; Coroutines, Ranges and Networking TSes published) : cpp". 15 July 2017.
- ↑ "where (generic type constraint)". learn.microsoft.com. Microsoft Learn. 30 July 2024.
- ↑ "Bounded Type Parameters". docs.oracle.com. Oracle Corporation. Retrieved 15 October 2025.
- ↑ "Generics: in, out, where Kotlin". kotlinlang.org. JetBrains s.r.o. Retrieved 15 October 2025.
References
edit- Sutton, Andrew; Stroustrup, Bjarne (2011). Design of Concept Libraries for C++ (PDF). International Conference on Software Language Engineering.
- Sutton, Andrew (October 2015). "Introducing Concepts". Overload. 129. ACCU.
- Sutton, Andrew (February 2016). "Defining Concepts". Overload. 131. ACCU.
External links
edit- cppreference.com Constraints and Concepts
- Stroustrup, Bjarne (26 February 2016). "a bit of background for concepts and C++17". isocpp.org.