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

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

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];
  1. Отметьте это B частная основа. Как это работает?

  2. Отметьте это operator B*() константа. Почему это важно?

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

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

116
задан quantum 21 July 2017 в 23:23
поделиться

4 ответа

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

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

D* (Host<B, D>&)

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

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

B* (Host<B, D> const&)
D* (Host<B, D>&)

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

Если убрать const, то мы получим следующих кандидатов

B* (Host<B, D>&)
D* (Host<B, D>&)

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

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

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

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

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

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

D* (Host<B, D>&) 

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

B* (Host<B, D> const&)

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

108
ответ дан 24 November 2019 в 02:18
поделиться

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

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

Далее рассмотрим разрешение перегрузки. Первая перегрузка - это инстанцирование шаблона 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, отсутствие check((), int()) будет неоднозначным. В результате выбирается шаблонизированный yes check(D*, int). Однако, если D не наследуется от B, то no check((), int()) не будет двусмысленным. На данный момент разрешение перегрузки не может происходить на основе кратчайшей последовательности преобразования. Однако при равных последовательностях преобразования разрешение перегрузки предпочитает нешаблонные функции, т.е. нет check(B*, int).

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

24
ответ дан 24 November 2019 в 02:18
поделиться

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

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

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

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

2
ответ дан 24 November 2019 в 02:18
поделиться

Бит private полностью игнорируется 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 является частной базой, не препятствует проверке, он только препятствует преобразованию, но мы никогда не спрашиваем о фактическом преобразовании ;)

.
4
ответ дан 24 November 2019 в 02:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: