Как работает `is_base_of`?

Как работает следующий код?

typedef char (&yes)[1]; typedef char (&no)[2]; template  struct Host { operator B*() const; operator D*(); }; template  struct is_base_of { template  static yes check(D*, T); static no check(B*, int); static const bool value = sizeof(check(Host(), int())) == sizeof(yes); }; //Test sample class Base {}; class Derived : private Base {}; //Expression is true. int test[is_base_of::value && !is_base_of::value]; 
  1. Обратите внимание, что B является частной базой. Как это работает?

  2. Заметим, что operator B*() является const. Почему это важно?

  3. Почему template static yes check(D*, T); лучше, чем static yes check(B*, int); ?

Примечание . Уменьшена версия (macros удалены) boost::is_base_of . И это работает на широком спектре компиляторов.

Если они связаны

Давайте на мгновение предположим, что B фактически является базой D Затем для check вызова обе версии являются жизнеспособными, поскольку Host может быть преобразован в D* и B* . Это пользовательская последовательность преобразований, как описано в 13.3.3.1.2 от Host до D* и B* соответственно. Для нахождения функций преобразования, которые могут преобразовывать class, для первой функции check синтезируются следующие функции кандидата согласно 13.3.1.5/1

 D* (Host&) 

Первая функция преобразования не является кандидатом, потому что B* не может быть преобразован в D* .

Для второй функции существуют следующие кандидаты:

 B* (Host const&) D* (Host&) 

Это два кандидата функции преобразования, которые принимают объект-хост. Первый принимает его по ссылке const, а второй – нет. Таким образом, второе является лучшим совпадением для неконстантного *this объекта ( подразумеваемого аргумента объекта ) на 13.3.3.2/3b1sb4 и используется для преобразования в B* для второй функции check .

Если вы удалите const, у нас будут следующие кандидаты

 B* (Host&) D* (Host&) 

Это означало бы, что мы больше не можем выбирать созвездием. В обычном сценарии разрешения перегрузки вызов теперь будет неоднозначным, поскольку обычно тип возврата не будет участвовать в разрешении перегрузки. Однако для функций преобразования есть бэкдор. Если две функции преобразования одинаково хороши, тогда тип возврата определяет, кто лучше всего соответствует 13.3.3/1 . Таким образом, если вы удалите константу, то первая будет взята, потому что B* лучше преобразуется в B* чем D* в B* .

Теперь какая пользовательская последовательность преобразования лучше? Один для второй или первой функции проверки? Правило состоит в том, что пользовательские последовательности преобразования могут сравниваться только в том случае, если они используют одну и ту же функцию преобразования или конструктор согласно 13.3.3.2/3b2 . Это как раз здесь: обе используют вторую функцию преобразования. Обратите внимание, что, таким образом, константа важна, потому что она вынуждает компилятор взять вторую функцию преобразования.

Так как мы можем сравнить их – какой из них лучше? Правило заключается в том, что лучшее преобразование из возвращаемого типа функции преобразования в тип назначения выигрывает (опять же на 13.3.3.2/3b2 ). В этом случае D* лучше преобразуется к D* чем к B* . Таким образом, первая функция выбрана, и мы узнаем наследование!

Обратите внимание: поскольку мы никогда не нуждались в том, чтобы фактически преобразовать в базовый class, мы можем тем самым распознать частное наследование, потому что, можем ли мы преобразовать из D* в B* , не зависит от формы наследования в соответствии с 4.10/3

Если они не связаны

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

 D* (Host&) 

А для второго у нас теперь есть другой набор

 B* (Host const&) 

Поскольку мы не можем преобразовать D* в B* если у нас нет отношения наследования, теперь у нас нет общей функции преобразования среди двух пользовательских последовательностей преобразования! Таким образом, мы были бы двусмысленными, если бы не тот факт, что первая функция является шаблоном. Шаблоны являются вторым выбором, если есть функция без шаблона, которая одинаково хороша в соответствии с 13.3.3/1 . Таким образом, мы выбираем функцию без шаблона (вторая), и мы признаем, что наследование между B и D !

Давайте рассмотрим, как это работает, глядя на шаги.

Начните с sizeof(check(Host(), int())) . Компилятор может быстро увидеть, что эта check(...) является выражением вызова функции, поэтому при check необходимо выполнить разрешение перегрузки. Доступны две потенциальные перегрузки, template yes check(D*, T); и no check(B*, int); , Если выбрано первое, вы получаете sizeof(yes) , иначе sizeof(no)

Затем давайте посмотрим на разрешение перегрузки. Первой перегрузкой является check (D*, T=int) экземпляра шаблона check (D*, T=int) а второй кандидат – check(B*, int) . Фактическими аргументами являются Host и int() . Второй параметр явно не различает их; он просто помог сделать первую перегрузку шаблоном. Позже мы увидим, почему важна часть шаблона.

Теперь посмотрим на последовательности преобразования, которые необходимы. Для первой перегрузки у нас есть Host::operator D* – одно пользовательское преобразование. Во-вторых, перегрузка сложнее. Нам нужен B *, но возможны две последовательности преобразования. Один из них – через Host::operator B*() const . Если (и только если) B и D связаны наследованием, будет существовать последовательность преобразования Host::operator D*() + D*->B* . Теперь предположим, что D действительно наследует от B. Две последовательности преобразования: Host -> Host const -> operator B* const -> B* и Host -> operator D* -> D* -> B* .

Таким образом, для связанных B и D no check((), int()) бы двусмысленной. В результате выбирается шаблонная yes check(D*, int) . Однако, если D не наследует от B, то no check((), int()) не является двусмысленной. На данный момент разрешение перегрузки не может быть выполнено в кратчайшей последовательности преобразований. Однако, учитывая равные последовательности преобразования, разрешение перегрузки предпочитает нешаблонные функции, т. Е. no check(B*, int) .

Теперь вы видите, почему не имеет значения, что наследование является приватным: это отношение только для устранения no check(Host(), int()) от разрешения перегрузки до проверки доступа. И вы также видите, почему operator B* const должен быть const: иначе нет необходимости в Host -> Host const , никакой двусмысленности, и no check(B*, int) будет всегда выбираются.

is_base_of бит полностью игнорируется is_base_of поскольку до проверки доступности происходит перегрузка.

Вы можете проверить это просто:

 class Foo { public: void bar(int); private: void bar(double); }; int main(int argc, char* argv[]) { Foo foo; double d = 0.3; foo.bar(d); // Compiler error, cannot access private member function } 

То же самое касается и того факта, что B является частной базой, не мешает проведению проверки, это только предотвратит преобразование, но мы никогда не запрашиваем фактическое преобразование;)

Возможно, это связано с частичной упорядоченностью разрешения перегрузки. D * является более специализированным, чем B *, в случае, когда D происходит от B.

Точные детали довольно сложны. Вы должны выяснить приоритеты различных правил разрешения перегрузки. Частичное упорядочение – одно. Длины / виды конверсионных последовательностей – еще один. Наконец, если две жизнеспособные функции считаются одинаково хорошими, то не шаблоны выбираются по шаблонам функций.

Мне никогда не приходилось искать, как эти правила взаимодействуют. Но кажется, что частичный порядок доминирует над другими правилами разрешения перегрузки. Когда D не вытекает из B, правила частичного упорядочения не применяются, а не шаблон является более привлекательным. Когда D происходит от B, частичное упорядочение срабатывает и делает шаблон функции более привлекательным – как кажется.

Что касается назначения наследования: код никогда не запрашивает преобразование из D * в B *, что потребует публичного наследования.

Следуя вашему второму вопросу, обратите внимание, что если бы это не было для const, Host был бы плохо сформирован, если бы он был создан с B == D. Но is_base_of сконструирован таким образом, что каждый class является базой самого себя, поэтому один из операторов преобразования должен const.

  • Как убедиться, что параметр шаблона является подтипом требуемого типа?
  • Статический polymorphism C ++ (CRTP) и использование typedefs из производных classов
  • Дизайн шаблонов веб-приложений
  • Вызов функции для каждого вариационного аргумента шаблона и массива
  • Каковы некоторые параметры шаблона шаблона?
  • проверьте, существует ли элемент с помощью enable_if
  • Проблема с шаблоном вызывает ошибку компоновщика (C ++)
  • В чем отличия между Mustache.js и Handlebars.js?
  • Как реализовать макрос (или шаблон) no-op в C ++?
  • AngularJS - визуализация на стороне сервера
  • В шаблоном производном classе, почему мне нужно квалифицировать имена членов базового classа с помощью «this->» внутри функции-члена?
  • Давайте будем гением компьютера.