Как работает следующий код?
typedef char (&yes)[1];
typedef char (&no)[2];
template <typename B, typename D>
struct Host
{
operator B*() const;
operator D*();
};
template <typename B, typename D>
struct is_base_of
{
template <typename T>
static yes check(D*, T);
static no check(B*, int);
static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};
//Test sample
class Base {};
class Derived : private Base {};
//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
Обратите внимание, что
B
это частная база. Как это работает?Обратите внимание, что
operator B*()
это const. Почему это важно?Почему
template<typename T> static yes check(D*, T);
лучше чемstatic yes check(B*, int);
?
Примечание : это уменьшенная версия (макросы удалены) boost::is_base_of
. И это работает на широком спектре компиляторов.
c++
templates
overloading
implicit-conversion
typetraits
Алексей Малистов
источник
источник
is_base_of
: ideone.com/T0C1V Однако она не работает со старыми версиями GCC (GCC4.3 отлично работает).is_base_of<Base,Base>::value
должно бытьtrue
; это возвращаетсяfalse
.Ответы:
Если они связаны
Давайте на мгновение предположим, что
B
на самом деле это базаD
. Тогда для вызоваcheck
обе версии жизнеспособны, потому чтоHost
могут быть преобразованы вD*
иB*
. Это определяется пользователем последовательность преобразования , как описано13.3.3.1.2
отHost<B, D>
кD*
иB*
соответственно. Для поиска функций преобразования, которые могут преобразовать класс, следующие функции-кандидаты синтезируются для первойcheck
функции в соответствии с13.3.1.5/1
Первая функция преобразования не является кандидатом, потому что
B*
не может быть преобразована вD*
.Для второй функции существуют следующие кандидаты:
Это два кандидата в функции преобразования, которые принимают ведущий объект. Первый принимает его по константной ссылке, а второй - нет. Таким образом, второй лучше подходит для неконстантного
*this
объекта ( подразумеваемого аргумента объекта )13.3.3.2/3b1sb4
и используется для преобразования вB*
для второгоcheck
функции.Если вы удалите константу, у нас будут следующие кандидаты
Это означало бы, что мы больше не можем выбирать по константе. В обычном сценарии разрешения перегрузки вызов теперь будет неоднозначным, поскольку обычно возвращаемый тип не участвует в разрешении перегрузки. Однако для функций преобразования есть лазейка. Если две функции преобразования одинаково хороши, то их возвращаемый тип решает, кто из них лучше
13.3.3/1
. Таким образом, если вы удалите константу, тогда будет взята первая, потому чтоB*
лучше преобразуется в,B*
чемD*
вB*
.Какая последовательность преобразования, определяемая пользователем, лучше? Один для второй или первой функции проверки? Правило состоит в том, что определенные пользователем последовательности преобразования можно сравнивать, только если они используют одну и ту же функцию преобразования или конструктор в соответствии с
13.3.3.2/3b2
. Здесь именно так: оба используют вторую функцию преобразования. Обратите внимание, что таким образом const важна, потому что она заставляет компилятор выполнять вторую функцию преобразования.Раз уж мы можем их сравнивать - какой из них лучше? Правило состоит в том, что лучшее преобразование из возвращаемого типа функции преобразования в целевой тип выигрывает (опять же
13.3.3.2/3b2
). В этом случаеD*
лучше преобразуется в,D*
чем вB*
. Таким образом выбирается первая функция и мы распознаем наследование!Обратите внимание, что, поскольку нам никогда не нужно было фактически преобразовывать в базовый класс, мы можем таким образом распознать частное наследование, потому что возможность преобразования из a
D*
в aB*
не зависит от формы наследования в соответствии с4.10/3
Если они не связаны
Теперь предположим, что они не связаны по наследству. Таким образом, для первой функции у нас есть следующие кандидаты
И на секунду у нас теперь есть еще один набор
Поскольку мы не можем
D*
выполнить преобразование в,B*
если у нас нет отношения наследования, у нас теперь нет общей функции преобразования для двух пользовательских последовательностей преобразования! Таким образом, мы были бы двусмысленными, если бы не тот факт, что первая функция является шаблоном. Шаблоны - это второй вариант, когда есть функция, не являющаяся шаблоном, которая, согласно13.3.3/1
. Таким образом, мы выбираем нешаблонную функцию (вторую) и понимаем, что нет наследования междуB
иD
!источник
std::is_base_of<...>
. Все под капотом.boost::
прежде, должны убедиться, что у них есть эти встроенные функции, прежде чем использовать их. И у меня такое чувство, что среди них есть какой-то менталитет «принять вызов», чтобы реализовать что-то без помощи компилятора :)Давайте разберемся, как это работает, посмотрев на шаги.
Начнем с
sizeof(check(Host<B,D>(), int()))
части. Компилятор может быстро увидеть, что этоcheck(...)
выражение вызова функции, поэтому ему необходимо выполнить разрешение перегрузкиcheck
. Доступны две возможные перегрузки:template <typename T> yes check(D*, T);
иno check(B*, int);
. Если выбрано первое, вы получитеsizeof(yes)
, иначеsizeof(no)
Далее посмотрим на разрешение перегрузки. Первая перегрузка - это экземпляр шаблона,
check<int> (D*, T=int)
а второй кандидат -check(B*, int)
. Фактические аргументы:Host<B,D>
иint()
. Второй параметр их явно не различает; он просто служил для превращения первой перегрузки в шаблонную. Позже мы увидим, почему часть шаблона актуальна.Теперь посмотрим на необходимые последовательности преобразования. Для первой перегрузки у нас есть
Host<B,D>::operator D*
- одно определяемое пользователем преобразование. Для второго сложнее перегрузка. Нам нужен B *, но возможны две последовательности преобразования. Один - черезHost<B,D>::operator B*() const
. Если (и только если) B и D связаны наследованием, последовательность преобразованияHost<B,D>::operator D*()
+ будетD*->B*
существовать. Теперь предположим, что D действительно наследуется от B. Две последовательности преобразования - этоHost<B,D> -> Host<B,D> const -> operator B* const -> B*
иHost<B,D> -> operator D* -> D* -> B*
.Таким образом, для родственных B и D это
no check(<Host<B,D>(), int())
будет неоднозначным. В итогеyes check<int>(D*, int)
выбирается шаблонный . Однако, если D не наследуется от B,no check(<Host<B,D>(), int())
это не является неоднозначным. На этом этапе разрешение перегрузки не может быть основано на кратчайшей последовательности преобразования. Тем не менее, данные последовательности равно преобразования, разрешение перегрузки предпочитает функцию без шаблона, то естьno check(B*, int)
.Теперь вы понимаете, почему не имеет значения, что наследование является частным: это отношение служит только для исключения
no check(Host<B,D>(), int())
из разрешения перегрузки до того, как произойдет проверка доступа. И вы также понимаете, почемуoperator B* const
должен быть const: иначе нет необходимости вHost<B,D> -> Host<B,D> const
шаге, нет двусмысленности иno check(B*, int)
всегда будет выбран.источник
const
. Если ваш ответ верный, то в этом нетconst
необходимости. Но это не так. Удалитьconst
и обмануть не получится.no check(B*, int)
больше не являются неоднозначными.no check(B*, int)
, то для родственныхB
иD
неоднозначно не будет. Компилятор однозначно выберетoperator D*()
выполнение преобразования, потому что у него нет константы. Это скорее немного в противоположном направлении: если вы удалите константу, вы введете некоторую двусмысленность, но она разрешается тем фактом, чтоoperator B*()
предоставляет превосходный возвращаемый тип, который не требует преобразования указателя вB*
подобныеD*
.B*
из<Host<B,D>()
временного.private
Бит полностью игнорируется ,is_base_of
поскольку разрешение перегрузки происходит до доступности проверок.Проверить это можно просто:
То же самое применимо и здесь, тот факт, что
B
это частная база, не препятствует выполнению проверки, это только предотвратит преобразование, но мы никогда не запрашиваем фактическое преобразование;)источник
host
произвольно преобразуется в невычисленное выражениеD*
илиB*
в него. По некоторым причинам при определенных условияхD*
предпочтительнееB*
.Возможно, это как-то связано с частичным упорядочиванием по разрешению перегрузки. D * более специализирован, чем B * в случае, если D происходит от B.
Точные детали довольно сложны. Вы должны выяснить приоритеты различных правил разрешения перегрузки. Частичный заказ - один. Длина / виды последовательностей преобразования - это еще один вопрос. Наконец, если две жизнеспособные функции считаются одинаково хорошими, не шаблоны предпочтительнее шаблонов функций.
Мне никогда не приходилось смотреть, как эти правила взаимодействуют. Но похоже, что частичный порядок доминирует над другими правилами разрешения перегрузки. Когда D не является производным от B, правила частичного упорядочивания не применяются, и более привлекательным является не шаблон. Когда D происходит от B, срабатывает частичное упорядочение, которое, как кажется, делает шаблон функции более привлекательным.
Что касается частного наследования: код никогда не запрашивает преобразование из D * в B *, которое потребовало бы публичного наследования.
источник
is_base_of
и циклов, через которые участники прошли, чтобы гарантировать это.The exact details are rather complicated
- в этом-то и дело. Пожалуйста, объясни. Я действительно хочу знать.Следуя вашему второму вопросу, обратите внимание, что если бы не const, Host был бы неправильно сформирован, если бы экземпляр был создан с помощью B == D. Но is_base_of разработан таким образом, что каждый класс является базой самого себя, поэтому один из операторов преобразования должен быть константой.
источник