Given :
#include <concepts> #include <iostream> template<class T> struct wrapper; template<std::signed_integral T> struct wrapper<T> { wrapper() = default; void print() { std::cout << "signed_integral" << std::endl; } }; template<std::integral T> struct wrapper<T> { wrapper() = default; void print() { std::cout << "integral" << std::endl; } }; int main() { wrapper<int> w; w.print(); // Output : signed_integral return 0; }
From code above, int
qualifies to both std::integral
and std::signed_integral
concept.
Surprisingly this compiles and prints "signed_integral" on both GCC and MSVC compilers. I was expecting it to fail with an error along the lines of "template specialization already been defined".
Okay, that's legal, fair enough, but why was std::signed_integral
chosen instead of std::integral
? Is there any rules defined in the standard with what template specialization gets chosen when multiple concepts qualify for the template argument?
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
Template in C++is a feature. We write code once and use it for any data type including user defined data types. For example, sort() can be written and used to sort any data type items. A class stack can be created that can be used as a stack of any data type.
For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.
In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
This is because concepts can be more specialized than others, a bit like how template order themselves. This is called partial ordering of constraints
In the case of concepts, they subsumes each other when they include equivalent constraints. For example, here's how std::integral
and std::signed_integral
are implemented:
template<typename T> concept integral = std::is_integral_v<T>; template<typename T> // v--------------v---- Using the contraint defined above concept signed_integral = std::integral<T> && std::is_signed_v<T>;
Normalizing the constraints the compiler boil down the contraint expression to this:
template<typename T> concept integral = std::is_integral_v<T>; template<typename T> concept signed_integral = std::is_integral_v<T> && std::is_signed_v<T>;
In this example, signed_integral
implies integral
completely. So in a sense, a signed integral is "more constrained" than an integral.
The standard writes it like this:
From [temp.func.order]/2 (emphasis mine):
Partial ordering selects which of two function templates is more specialized than the other by transforming each template in turn (see next paragraph) and performing template argument deduction using the function type. The deduction process determines whether one of the templates is more specialized than the other. If so, the more specialized template is the one chosen by the partial ordering process. If both deductions succeed, the partial ordering selects the more constrained template as described by the rules in [temp.constr.order].
That means that if there is multiple possible substitution for a template and both are choosen from partial ordering, it will select the most constrained template.
From [temp.constr.order]/1:
A constraint P subsumes a constraint Q if and only if, for every disjunctive clause Pi in the disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in the conjunctive normal form of Q, where
a disjunctive clause Pi subsumes a conjunctive clause Qj if and only if there exists an atomic constraint Pia in Pi for which there exists an atomic constraint Qjb in Qj such that Pia subsumes Qjb, and
an atomic constraint A subsumes another atomic constraint B if and only if A and B are identical using the rules described in [temp.constr.atomic].
This describe the subsumption algorithm that compiler use to order constraints, and therefore concepts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With