Dany :
#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;
}
Z powyższego kodu int
kwalifikuje się zarówno do, jak std::integral
i do std::signed_integral
koncepcji.
Zaskakujące jest to, że kompiluje i drukuje „podpisany_integralny” zarówno na kompilatorach GCC, jak i MSVC. Spodziewałem się, że zakończy się niepowodzeniem z błędem w stylu „już zdefiniowano specjalizację szablonu”.
W porządku, to legalne, dość uczciwe, ale dlaczego std::signed_integral
zamiast tego wybrano std::integral
? Czy istnieją jakieś reguły zdefiniowane w standardzie, przy wyborze specjalizacji szablonu, gdy do argumentu szablonu kwalifikuje się wiele pojęć?
c++
language-lawyer
c++20
c++-concepts
Lewis Liman
źródło
źródło
Odpowiedzi:
Wynika to z faktu, że koncepcje mogą być bardziej wyspecjalizowane niż inne, trochę podobnie jak same szablony. Nazywa się to częściowym uporządkowaniem ograniczeń
W przypadku pojęć wzajemnie się zapełniają, gdy zawierają równoważne ograniczenia. Na przykład, oto jak
std::integral
istd::signed_integral
są realizowane:Normalizując ograniczenia kompilator sprowadza wyrażenie ograniczenia do tego:
W tym przykładzie
signed_integral
oznaczaintegral
całkowicie. W pewnym sensie całka ze znakiem jest „bardziej ograniczona” niż całka.Standard pisze to tak:
Od [temp.func.order] / 2 ( wyróżnienie moje):
Oznacza to, że jeśli istnieje wiele możliwych podstawień dla szablonu i oba są wybrane z częściowego uporządkowania, wybierze najbardziej ograniczony szablon.
Od [temp.constr.order] / 1 :
Opisuje algorytm sumowania wykorzystywany przez kompilator do porządkowania ograniczeń, a zatem pojęć.
źródło
C ++ 20 ma mechanizm decydujący, kiedy jeden konkretny ograniczony podmiot jest „bardziej ograniczony” niż inny. To nie jest prosta sprawa.
Zaczyna się od koncepcji rozbicia wiązania na jego komponenty atomowe, procesu zwanego normalizacją wiązania . Jest tu zbyt duży i zbyt skomplikowany, aby go tu wchodzić, ale podstawową ideą jest to, że każde wyrażenie w ograniczeniu jest rekurencyjnie dzielone na atomowe elementy pojęciowe, aż do osiągnięcia podwyrażenia składowego, które nie jest pojęciem.
Biorąc to pod uwagę, spójrzmy jak zdefiniowane są pojęcia
integral
i :signed_integral
Rozkład
integral
jest sprawiedliwyis_integral_v
. Rozkładsigned_integral
jestis_integral_v && is_signed_v
.Teraz dochodzimy do koncepcji subsumcji ograniczeń . Jest to trochę skomplikowane, ale podstawową ideą jest to, że ograniczenie C1 mówi się, że „przejmuje” ograniczenie C2, jeśli rozkład C1 zawiera każde podwyrażenie w C2. Widzimy, że
integral
to nie ulega zniszczeniusigned_integral
, alesigned_integral
się zmniejszaintegral
, ponieważ zawiera wszystko, cointegral
robi.Następnie przechodzimy do zamawiania podmiotów z ograniczeniami:
Ponieważ
signed_integral
obejmujeintegral
,<signed_integral> wrapper
jest „co najmniej tak samo ograniczony” jak<integral> wrapper
. Jednak sytuacja odwrotna nie jest prawdą, ponieważ subskrypcja nie jest odwracalna.Dlatego zgodnie z zasadą dla „bardziej ograniczonych” podmiotów:
Ponieważ
<integral> wrapper
to nie jest co najmniej tak ograniczone jak to<signed_integral> wrapper
, drugie jest uważane za bardziej ograniczone niż pierwsze.A zatem, gdy obaj mogą się ubiegać, wygrywa deklaracja bardziej ograniczona.
Należy pamiętać, że reguły subsumcji ograniczeń kończą się, gdy napotkamy wyrażenie, które nie jest a
concept
. Więc jeśli to zrobiłeś:W takim przypadku
my_signed_integral
nie wziąłby udziałustd::integral
. Mimo żemy_is_integral_v
jest zdefiniowane identyczniestd::is_integral_v
, ponieważ nie jest to pojęcie, reguły subskrypcji C ++ nie mogą przejrzeć go w celu ustalenia, że są takie same.Reguły subskrypcji zachęcają więc do budowania pojęć z operacji na pojęciach atomowych.
źródło
Z częściowymi ograniczeniami
i
A koncepcja
std::signed_integral
obejmujestd::integral<T>
koncepcję:Twój kod jest w porządku, podobnie jak
std::signed_integral
bardziej „specjalistyczny”.źródło