问题
In a presentation of concepts something like this was shown:
template <bidirectional_iterator It>
void sort(It begin, It end); // #1
template <random_access_iterator It>
void sort(It begin, It end); // #2
std::list<int> l{};
sort(l.begin(), l.end()); // #A -> calls #1
std::vector<int> v{};
sort(v.begin(), v.end()); // #B -> calls #2
For the call #A it's simple: only sort #1 is viable as the constraint random_access_iterator is not satisfied so it calls #1.
But for the call #B both sorts are viable as both constraints (random_access_iterator and bidirectional_iterator are satisfied). So how is the "more efficient" sort #2 chosen? The presenter said "it just works".
回答1:
So how is the "more efficient"
sort #2chosen?
It works because there is a partial ordering on constraints (defined by the subsumes relation).
sort #2 (the one with the randomaccess_iterator) is more constrained than sort #1 (the one with bidirectional_iterator) because randomaccess_iterator subsumes bidirectional_iterator:
template <class It>
concept bidirectional_iterator = requires /*...*/;
template <class It>
concept randomaccess_iterator = bidirectional_iterator<It> && requires /*...*/;
To make this work constraints are aware at the language level of conjunctions and disjunctions.
The process for determining if a declaration is more or less constrained than another goes like this: Constraint normalization -> constraint subsumes relation -> (defines) constraint partial ordering -> (determines) declarations are more/less constraint relation.
Simplified, normalization is the substitution of the concepts template parameters in the parameter mapping of constraints.
Example:
template <class T> concept integral = std::is_integral_v<T>;
template <class T> concept signed_integral = integral<T> && std::is_signed_v<T>;
template <class T> concept integral_4 = integral<T> && sizeof(T) == 4;
void foo_1(integral auto) // #0
void foo_1(signed_integral auto) // #1
void foo_1(integral_4 auto) // #2
auto test1()
{
foo_1(std::uint16_t{}); // calls #0
foo_1(std::uint32_t{}); // calls #2
foo_1(std::int16_t{}); // calls #1
//foo_1(std::int32_t{}); // error ambiguous between #1 and #2
}
- the normal form of
integralisstd::is_integral_v<T> - the normal form of
signed_integralisstd::is_integral_v<T> ∧ std::is_signed_v<T> the normal form
integral_4isstd::is_integral_v<T> ∧ sizeof(T) == 4signed_integralsubsumesintegralintegral_4subsumesintegral#1is more constraint than#0#2is more constraint than#0
Example:
template <class T> concept integral = std::is_integral_v<T>;
template <class T> concept signed_integral_sad = std::is_integral_v<T> &&
std::is_signed_v<T>;
template <class T> concept integral_4_sad = std::is_integral_v<T> && sizeof(T) == 4;
void foo_2(integral auto) // #0
void foo_2(signed_integral_sad auto); // #1
void foo_2(integral_4_sad auto); // #2
auto test2()
{
foo_2(std::uint16_t{}); // calls #0
//foo_2(std::uint32_t{}); // error ambiguous between #0 and #2
//foo_2(std::int16_t{}); // error ambiguous between #0 and #1
//foo_2(std::int32_t{}); // error ambiguous between #0, #1 and #2
}
- the normal form of
integralisstd::is_integral_v<T> - the normal form of
signed_integral_sadisstd::is_integral_v<T> ∧ std::is_signed_v<T> - the normal form
integral_4_sadisstd::is_integral_v<T> ∧ sizeof(T) == 4
However there is a rule
§13.5.1.2 Atomic constraints [temp.constr.atomic]
- Two atomic constraints,
e1ande2, are identical if they are formed from the same appearance of the same expression [...]
This means that the std::is_integral_v<T> atomic expressions from the 3 normal forms are not identical between them because they were not formed from the same expression. So:
- there is no subsumes relation
- there is no more constraint relation
Which leads to the extra ambiguities.
§ 13.5.1 Constraints [temp.constr.constr]
A constraint is a sequence of logical operations and operands that specifies requirements on template arguments. The operands of a logical operation are constraints. There are three different kinds of constraints:
- (1.1) conjunctions (13.5.1.1)
- (1.2) disjunctions (13.5.1.1), and
- (1.3) atomic constraints (13.5.1.2).
§13.5.1.1 Logical operations [temp.constr.op]
- There are two binary logical operations on constraints: conjunction and disjunction. [Note: These logical operations have no corresponding C++ syntax. For the purpose of exposition, conjunction is spelled using the symbol ∧ and disjunction is spelled using the symbol ∨]
§13.5.3 Constraint normalization [temp.constr.normal]
The normal form of an expression E is a constraint (13.5.1) that is defined as follows:
- (1.1) The normal form of an expression
( E )is the normal form ofE.- (1.2) The normal form of an expression
E1 || E2is the disjunction (13.5.1.1) of the normal forms ofE1andE2.- (1.3) The normal form of an expression
E1 && E2is the conjunction of the normal forms ofE1andE2.- (1.4) The normal form of a concept-id
C<A1, A2, ..., An>is the normal form of the constraint-expression ofC, after substitutingA1, A2, ..., AnforC’s respective template parameters in the parameter mappings in each atomic constraint. [...]- (1.5) The normal form of any other expression
Eis the atomic constraint whose expression isEand whose parameter mapping is the identity mapping.The process of obtaining the normal form of a constraint-expression is called normalization.
§13.5.4 Partial ordering by constraints [temp.constr.order]
A constraint
Psubsumes a constraintQif and only if, for every disjunctive clausePiin the disjunctive normal form 130 ofP,Pisubsumes every conjunctive clauseQjin the conjunctive normal form 131 ofQ, where
- (1.1) a disjunctive clause
Pisubsumes a conjunctive clauseQjif and only if there exists an atomic constraintPiainPifor which there exists an atomic constraintQjbinQjsuch thatPiasubsumesQjb, and- (1.2) an atomic constraint
Asubsumes another atomic constraintBif and only ifAandBare identical using the rules described in 13.5.1.2.[Example: Let
AandBbe atomic constraints (13.5.1.2). The constraintA ∧ BsubsumesA, butAdoes not subsumeA ∧ B. The constraintAsubsumesA ∨ B, butA ∨ Bdoes not subsumeA. Also note that every constraint subsumes itself. — end example][Note: The subsumption relation defines a partial ordering on constraints. This partial ordering is used to determine
- (2.1) the best viable candidate of non-template functions (12.4.3),
- (2.2) the address of a non-template function (12.5),
- (2.3) the matching of template template arguments (13.4.3),
- (2.4) the partial ordering of class template specializations (13.7.5.2), and
- (2.5) the partial ordering of function templates (13.7.6.2).
— end note]
A declaration
D1is at least as constrained as a declarationD2if
- (3.1)
D1andD2are both constrained declarations andD1’s associated constraints subsume those ofD2; or- (3.2)
D2has no associated constraints.A declaration
D1is more constrained than another declarationD2whenD1is at least as constrained asD2, andD2is not at least as constrained asD1.130) A constraint is in disjunctive normal form when it is a disjunction of clauses where each clause is a conjunction of atomic constraints. [Example: For atomic constraints
A,B, andC, the disjunctive normal form of the constraintA ∧ (B ∨ C)is(A ∧ B) ∨ (A ∧ C). Its disjunctive clauses are(A ∧ B)and(A ∧ C). — end example]131) A constraint is in conjunctive normal form when it is a conjunction of clauses where each clause is a disjunction of atomic constraints. [Example: For atomic constraints
A,B, andC, the constraintA ∧ (B ∨ C)is in conjunctive normal form. Its conjunctive clauses areAand(B ∨ C). — end example
来源:https://stackoverflow.com/questions/61634127/how-is-the-best-constrained-function-template-selected-with-concepts