Используйте общие свойства для всех классов

C ++ 11

Проблема

Хотя правила в C ++ 03 о том, когда вам нужны typename и template, в значительной степени разумны, есть один неприятный недостаток его формулировки

template
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g();

    // OK
    g();

    // error, "A" is dependent, "typename" keyword needed
    A::result_type n1;

    // OK
    result_type n2; 
  }

  template
  void g();
};

. Как можно видеть, нам нужно ключевое слово disamiguation, даже если компилятор мог бы понять, что A::result_type может быть только int (и, следовательно, является типом) и this->g может быть только членом шаблона g, объявленного позже (даже если A явно специализирован где-то, что не повлияет на код внутри этого шаблона, поэтому его значение не может быть затронуто более поздней специализацией A !).

Текущая инстанция

Чтобы улучшить ситуацию, на C ++ 11 язык отслеживает, когда тип относится к охватывающему шаблону. Чтобы знать это, тип должен быть сформирован с использованием определенной формы имени, которая является ее собственным именем (в приведенных выше, A, A, ::A). Известно, что тип, на который ссылается такое имя, является текущим экземпляром . Может быть несколько типов, которые являются текущим экземпляром, если тип, из которого формируется имя, является членом / вложенным классом (тогда A::NestedClass и A являются текущими экземплярами).

Исходя из этого понятия, язык говорит, что CurrentInstantiation::Foo, Foo и CurrentInstantiationTyped->Foo (такие как A *a = this; a->Foo) являются участником текущего экземпляра , если они являются членами класса, который является текущим экземпляром или одним из его независящих базовых классов (просто выполнив поиск имени сразу).

Теперь ключевые слова typename и template больше не требуются, если определитель является членом текущего экземпляра. Здесь следует помнить, что A - все еще имя, зависящее от типа (ведь T также зависит от типа). Но A::result_type, как известно, является типом - компилятор будет «волшебным образом» смотреть на такого рода зависимые типы, чтобы понять это.

struct B {
  typedef int result_type;
};

template
struct C { }; // could be specialized!

template
struct D : B, C {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Это впечатляет, но можем ли мы сделать лучше? Язык даже идет дальше, и требует , чтобы реализация снова смотрела D::result_type при создании экземпляра D::f (даже если она нашла свое значение уже во время определения). Когда результат поиска отличается или приводит к двусмысленности, программа плохо сформирована и должна быть дана диагностика. Представьте, что произойдет, если мы определим C, как это

template<>
struct C {
  typedef bool result_type;
  typedef int questionable_type;
};

. Компилятор должен улавливать ошибку при создании экземпляра D::f. Таким образом, вы получаете лучший из двух миров: «Отложенный» поиск защищает вас, если вы можете столкнуться с проблемами с зависимыми базовыми классами, а также «немедленный» поиск, который освобождает вас от typename и template.

Неизвестные специализации

В коде D имя typename D::questionable_type не является членом текущего экземпляра. Вместо этого язык обозначает его как члена неизвестной специализации . В частности, это всегда происходит, когда вы делаете DependentTypeName::Foo или DependentTypedName->Foo, и либо зависимый тип , ни текущий экземпляр (в этом случае компилятор может отказаться и сказать «мы будем Посмотрите позже, что Foo), или - текущий экземпляр, и имя не было найдено в нем или его независящих базовых классах, и есть также зависимые базовые классы.

Представьте, что произойдет, если у нас была функция-член h в пределах указанного выше шаблона класса A

void h() {
  typename A::questionable_type x;
}

. В C ++ 03 язык позволил поймать эту ошибку, потому что никогда не может быть действительный способ создания экземпляра A::h (любой аргумент, который вы даете T). В C ++ 11 теперь у пользователя есть дополнительная проверка, чтобы дать больше причин для компиляторов реализовать это правило. Поскольку A не имеет зависимого базовые классы и A объявляют ни одного члена questionable_type, имя A::questionable_type не является ни членом текущего экземпляра , ни членом неизвестной специализации. в этом случае что этот код не может быть достоверно скомпилирован во время создания экземпляра, поэтому язык запрещает имя, в котором спецификатор является текущим экземпляром, не является ни членом неизвестной специализации, ни членом текущего экземпляра (однако это нарушение все еще не является

Примеры и мелочи

Вы можете попробовать это знание на этом ответе и посмотреть, действительно ли эти определения имеют смысл для вас реальный пример (в этом ответе они несколько менее детализированы).

Правила C ++ 11 делают неправильный код действующего C ++ 03 (который не был предназначен комитетом C ++, но, вероятно, не будет исправлен)

struct B { void f(); };
struct A : virtual B { void f(); };

template
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C c; c.g(); 
}

Этот действительный код C ++ 03 связывает this->f с A::f во время создания экземпляра, и все в порядке. Однако C ++ 11 привязывает его к B::f и требует двойной проверки при создании экземпляра, проверяя, соответствует ли поиск. Однако при создании экземпляра C::g применяется правило Dominance Rule , и поиск будет искать A::f.

0
задан Denis Wasilew 3 April 2019 в 09:09
поделиться