This article has multiple issues. Please helpimprove it or discuss these issues on thetalk page.(Learn how and when to remove these messages) (Learn how and when to remove this message)
|
Concepts are an extension to thetemplates feature provided by theC++ programming language. Concepts are namedBoolean predicates on template parameters, evaluated atcompile time. A concept may be associated with a template (class template,function template,member function of a class template,variable template, oralias template), in which case it serves as aconstraint: it limits the set of arguments that are accepted as template parameters.
Originally dating back to suggestions forC++11, the original concepts specification has been revised multiple times before formally being standardised inC++20.
The main uses of concepts are:
There are five different places in a function template signature where a constraint can be used (labeled below from 1 through 5):[1]
template<Concept1T>requiresConcept2<T>Concept3automyFunction(Concept4autoparam)requiresConcept5<T>;
Concept1: A type-constraint. This kind replacesclass ortypename for declaring atype template 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 forplaceholder variable aka.auto variable. C++20 addedabbreviated function templates which useauto as a placeholder type in the parameter declaration.[2] 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 toConcept2 with 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 formsConcept1 andConcept2 can be used in all kinds of templates.
The following demonstrates using a concept as an upper bound for inheritance constraints on types, by creating a conceptExtendsUser satisfied only by classes which inherit from a base classPlayer, blocking any type that does not.
importstd;usingstd::is_base_of_v;usingstd::vector;classPlayer{// ...};template<typenameT>conceptExtendsPlayer=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 Playertemplate<ExtendsPlayerT>voidprocessListOfPlayers(vector<T>players){// ...}
This is similar toconstrained generics inJava, and is equivalent to the following example:
importjava.util.List;classPlayer{// ...}publicclassExample{// T is constrained to types that inherit from Playerpublicstatic<TextendsPlayer>voidprocessListOfPlayers(List<T>players){// ...}}
The following is a declaration of the conceptstd::equality_comparable from the<concepts> header of theC++ Standard Library. This concept is satisfied by any typeT such that forlvaluesa andb of typeT, the expressionsa == b anda != b as well as the reverseb == a andb != a compile, and their results are convertible to a type that satisfies the concept "boolean-testable":
namespacestd{// The following concept is an implementation detail used to build equality_comparabletemplate<typenameT,typenameU>conceptweakly_equality_comparable_with=requires(constremove_reference<T>&a,constremove_reference<U>&b){{a==b}->same_as<bool>;{a!=b}->same_as<bool>;{b==a}->same_as<bool>;{b!=a}->same_as<bool>;};template<typenameT>conceptequality_comparable=weakly_equality_comparable_with<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)voidf(constequality_comparableauto&);
or
// constrained function template declaration// using a type constraint (Concept1 from above)template<equality_comparableT>voidf(constT&);
And may be called as usual:
// OK, int satisfies equality_comparablef(42);
If 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 berandom-access iterators. If an argument is not an iterator, or is an iterator of a different category, an error will occur whenstd::sort attempts to use its parameters as bidirectional iterators. Considerstd::list<T>, which implements a doubly-linked list, and is not random-access:
usingstd::list;// std::list is typically a doubly-linked list, whose iterators are not random-accesslist<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
Concepts can be used to choose function template overloads and class template specializations based on properties of their template arguments, as an alternative toSFINAE andtag dispatching. If an argument satisfies more than one concept, the overload associated with the more constrained concept is chosen.
Concepts may be used instead of the unconstrained type deduction placeholderauto in variable declarations and function return types:
autox1=f(y);// the type of x1 is deduced to whatever f returnsSortableautox2=f(y);// the type of x2 is deduced, but only compiles if it satisfies Sortable
Concepts TS, as specified in ISO/IEC TS 19217:2015, are implemented as an experimental feature inGCC 6.[3] C++20 concepts are fully implemented inGCC 10,[4]MSVC 19.30,[5] andClang 10.[6]
A different form of Concepts, popularly known as "C++0x Concepts", was temporarily accepted into the working paper forC++11 but was removed in 2009.[7] In addition to concepts themselves, "C++0x Concepts" includedconcept maps (a feature that could make it possible, for example, for the concept "Stack" to acceptstd::vector, automatically mapping "Stack" operations such aspush() to differently named operations onstd::vector, such aspush_back()) andaxioms (a facility to specify semantic properties such as associativity or commutativity, allowing the compiler to take advantage of these properties without proof).
In contrast to this abandoned proposal, the C++20 version of Concepts is sometimes referred to as "Concepts Lite".[8]
During the C++ standards committee meeting in March 2016, the evolution working group moved to merge Concepts into the mainlineC++17 standard, but the motion was defeated in full committee.[9]
Concepts v1 was merged into theC++20 draft.[10]
"The One Range" version of Range feature that depend on concepts was also merged intoC++20.
InC#, a generic type constraint is expressed with awhere clause, which can be as expressive as concepts but are not named.[11]
usingSystem;publicclassMyGenericClass<T,U>whereT:IComparable<T>,allowsrefstructwhereU:class,notnull,new(){// ...}
Java haswildcard generics, which are not as expressive as concepts but can represent bounds on types.[12]
importjava.util.List;importjava.util.stream.Collectors;publicstatic<TextendsCharSequence&Comparable<T>>List<String>copyWhenGreater(List<T>list,Tthreshold){returnlist.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#-stylewhere clauses:[13]
fun<T>copyWhenGreater(list:List<T>,threshold:T):List<String>whereT:CharSequence,T:Comparable<T>{returnlist.filter{it>threshold}.map{it.toString()}}
Rust also useswhere clauses to bound traits.
usestd::cmp::Ord;structMyStruct<T>whereT:Ord+Default{value:T}impl<T>MyStruct<T>whereT:Ord+Default{fnnew(value:T)->Self{MyStruct{value}}fnis_less_than(&self,other:&Self)->bool{self.value<other.value}}