Использование шаблона внутренней функции std :: variant [duplicate]

Суть в том, что слово reference в выражении «передать по ссылке» означает нечто совершенно отличное от обычного значения слова reference в Java.

Обычно в Java ссылка означает ссылку на объект . Но технические термины проходят по ссылке / значению из теории языка программирования, говорят о ссылке на ячейку памяти, в которой хранится переменная , что является чем-то совершенно другим.

914
задан MSalters 1 June 2016 в 12:28
поделиться

6 ответов

943
ответ дан Alan 5 September 2018 в 07:20
поделиться

C ++ 11

Проблема

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

template<typename T>
struct A {
  typedef int result_type;

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

    // OK
    g<float>();

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

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

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

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

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

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

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

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  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<T> 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<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

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

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

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

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

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

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

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

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

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

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

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

int main() { 
  C<A> c; c.g(); 
}

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

120
ответ дан Community 5 September 2018 в 07:20
поделиться

Этот ответ должен быть довольно коротким и сладким, чтобы ответить (часть) озаглавленного вопроса. Если вы хотите получить более подробный ответ, объясняющий, почему вы должны их там поместить, пожалуйста, перейдите здесь .


Общее правило для размещения typename в основном, когда вы используете параметр шаблона, и хотите получить доступ к вложенному typedef или с использованием псевдонима, например:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

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

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Общие правила добавления определителя template в основном аналогичны, за исключением они обычно включают шаблонные функции-члены (статические или другие) структуры / класса, которые сами шаблоны, например:

Учитывая эту структуру и функцию:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Попытка доступа t.get<int>() изнутри функции приведет к ошибке:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Таким образом, в этом контексте вам понадобится ключевое слово template заранее и вызвать его так:

t.template get<int>()

Таким образом, компилятор будет анализировать это правильно, а не t.get < int.

77
ответ дан Community 5 September 2018 в 07:20
поделиться
typedef typename Tail::inUnion<U> dummy;

Однако я не уверен, что реализация inUnion верна. Если я правильно понимаю, этот класс не должен быть создан, поэтому вкладка «fail» никогда не будет автоматически терпеть неудачу. Возможно, было бы лучше указать, находится ли тип в объединении или нет с простым булевым значением.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: Посмотрите на Boost :: Variant

PS2: посмотрите на typelists , особенно в книге Андрея Александреску: Modern C ++ Design

18
ответ дан Luc Touraille 5 September 2018 в 07:20
поделиться

Я помещаю превосходный ответ JLBorges на аналогичный вопрос дословно из cplusplus.com, так как это наиболее краткое объяснение, которое я прочитал по этому вопросу.

] В шаблоне, который мы пишем, есть два типа имен, которые можно использовать - зависимые имена и не зависимые имена. Зависимое имя - это имя, которое зависит от параметра шаблона; неизменяемое имя имеет то же значение, независимо от параметров шаблона.

Например:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

То, что зависит от зависимого имени, может быть чем-то другим для каждого конкретного экземпляра шаблона. Как следствие, шаблоны C ++ подвержены «двухфазному поиску имен». Когда шаблон сначала анализируется (до того, как выполняется какое-либо создание), компилятор просматривает не зависящие имена. Когда происходит конкретное создание шаблона, параметры шаблона известны к тому времени, и компилятор ищет зависимые имена.

На первом этапе анализатор должен знать, является ли зависимое имя именем типа или имени не-типа. По умолчанию зависимым именем считается имя не-типа.

Использовать ключевое слово typename только в объявлениях шаблонов и определениях, приведенных ниже.


у вас есть квалифицированное имя, которое относится к типу и зависит от параметра шаблона.

1
ответ дан Nik-Lz 5 September 2018 в 07:20
поделиться
77
ответ дан Community 5 September 2018 в 07:20
поделиться
Другие вопросы по тегам:

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