Почему ограничение типа «std::convertible_to» может использоваться только с одним аргументом шаблона?

#c #templates #constraints #c 20 #c -concepts

Вопрос:

Я прокручивал и просматривал стандарт и cppreference в течение нескольких часов безрезультатно, был бы очень признателен, если бы кто-нибудь объяснил мне это событие:

Я рассматриваю стандартную концепцию std::convertibe_to . Вот простой пример, который я действительно понимаю

 class A {};
class B : public A {};

std::convertible_to<A, B>; // false
std::convertible_to<B, A>; // true
 

Работает, как и ожидалось.

Теперь есть также еще один возможный способ его использования, который я не совсем понимаю

 void foo(std::convertible_to<A> auto x) { /* ... */ }
 

, и эта функция может легко принимать любой тип, конвертируемый в A. Это, однако, странно, потому что первый параметр шаблона («От») по существу отбрасывается и выводится при вызове функции. Эта следующая функция также будет работать, и я вполне уверен, что она на самом деле эквивалентна предыдущей

 template<typename T, std::convertible_to<T> S>
void foo(S x) { /* ... */ }
 

снова тип x выводится, когда мы вызываем foo .

Это работает, несмотря на то, что шаблон требует двух параметров. Я тоже пробовал std::derived_from , и, похоже, это сработало. Эта форма указания концепции с использованием только одного параметра шаблона даже появляется в самом стандарте, поэтому должен быть какой-то синтаксис, который ее объясняет.

Обратите внимание, что на самом деле существует только одна версия std::convertible_to , которая принимает два параметра шаблона.

Кто-нибудь может объяснить, почему это работает?

Комментарии:

1. eel.is/c черновик/временный параметр#4

Ответ №1:

 void foo( constraint<P0, P1, P2> auto x );
 

это примерно переводится как

 template<contraint<P0, P1, P2> X>
void foo( X x );
 

что примерно переводится как

 template<class X> requires constraint<X, P0, P1, P2>
void foo( X x );
 

обратите внимание, как тип X добавляется к аргументам шаблона ограничения.

Так что в вашем случае,

 template<typename T, std::convertible_to<T> S>
void foo(S x) { /* ... */ }
 

это примерно

 template<typename T, class S>
requires std::convertible_to<S, T>
void foo(S x) { /* ... */ }
 

(Я говорю грубо, потому что считаю, что они не совсем эквивалентны в тонких аспектах. Например, второй вводит имя X , в то время как первый этого не делает. И, вероятно, есть и другие различия подобного масштаба; я имею в виду, что понимание перевода даст вам понимание того, что переводится. Это не похоже for(:) for(;;) на соответствие по циклу; стандарт определяет for(:) циклы в терминах for(;;) циклов, что не соответствует тому, что я утверждаю выше.)

Комментарии:

1. Последнее предложение: s/not like/not unlike/ ?

2. @Quuxplusone Нет. for(:) циклы определяются как буквальный перевод написанного пользователем кода C в определенный фрагмент for(;;) кода (с некоторыми переменными с секретными именами). цикл. Это причудливая часть стандарта. Насколько я знаю, приведенная выше эквивалентность не так прямолинейна.

3. Ах, я вижу различие, которое вы проводите сейчас, хотя я думаю, что оно слишком тонкое для данного контекста. Поэтому лично я бы охарактеризовал это как «…не похоже, но и не похоже,…» 😉

4. @Quuxplusone почти, но не совсем, совсем не похоже

Ответ №2:

Существует несколько мест, где concept можно использовать имя, в которых первый аргумент концепции шаблона не указан в списке аргументов шаблона. Ограничение auto выводимой переменной является одним из них.

Первый аргумент в этих случаях предоставляется некоторым выражением, обычно использующим правила вывода аргументов шаблона. В случае параметра ограниченной функции первый аргумент определяется самой функцией шаблона. То есть, если вы вызовете foo(10) , вычет аргумента шаблона выведет параметр auto шаблона как an int . Поэтому полная концепция будет convertible_to<int, A> .