Вопрос

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

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*() это const Почему это важно?

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

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

Это было полезно?

Решение

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

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

D* (Host<B, D>&)

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

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

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

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

Если бы ты Удалить Const, у нас будет следующие кандидаты

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

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

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

Так как мы можем сравнить их - какой из них лучше? Правило состоит в том, что лучшее преобразование от возврата типа функции преобразования в тип назначения выигрывает (снова 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!

Другие советы

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

Начните с 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()) не неоднозначен. На данный момент разрешение перегрузки не может произойти в Baed на кратчайшей последовательности конверсии. Тем не менее, заданные равные последовательности преобразования, разрешение перегрузки предпочитает функции не шаблонов, то есть 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) всегда будет выбран.

То 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 Личная база не позволяет проходить проверку, это только предотвратит преобразование, но мы никогда не просим фактическое преобразование;)

Возможно, имеет отношение к частичному заказу в разрешении перегрузки. D * более специализирован, чем B * в случае d получает от B.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top