Определить тип параметра шаблона в шаблоне

Я пытаюсь реализовать общий связанный список и итератор связанного списка на С++. У меня есть структура узла следующим образом

template <typename T>
struct Node
{
    T m_data;
    Node<T>* m_next;
};

У меня также есть итератор связанного списка, который является шаблоном, поэтому он может генерировать как обычные, так и const итераторы.

template <typename NodeType>
class LinkedListIterator
{  
private:
    NodeType* m_node;
public:
    LinkedListIterator(NodeType* n);
    T& operator*() const;
};

Мой вопрос: как правильно объявить функцию operator*()? Я ожидаю, что что-то вроде следующего должно работать

LinkedListIterator<const Node<T>> my_iter(some_node_pointer);
*my_iter = new_value; // should not work

Я понимаю, что возвращать T в operator*() не имеет смысла, так как этот класс не имеет доступа к имени типа в классе Node.

Я нашел обходной путь, создав псевдоним для типа внутри класса Node вот так

template <typename T>
struct Node
{
    typedef T type_value;
    // rest of Node class...
};

и теперь я могу сделать следующее в своем классе итератора

template <typename NodeType>
class LinkedListIterator
{
public:
    typename NodeType::type_value& operator*() const;
};

Кажется, это работает и возвращает правильное значение. Так что мой вопрос действительно должен быть, это лучший способ реализовать это? Нужно ли мне иметь typedef для создания псевдонима, чтобы я мог использовать этот тип? Или есть способ определить тип внутри класса LinkedListIterator?


person nick2225    schedule 14.08.2020    source источник
comment
typedef (или лучше using в C++11 и более поздних версиях) — это один из способов справиться с этим, да. Другой способ — просто использовать auto, например: auto& operator*() const { return m_node->m_data; }   -  person Remy Lebeau    schedule 14.08.2020
comment
Псевдоним для типа внутри — это то, что постоянно делает сама библиотека C++. Вы будете удивлены, узнав о члене value_type каждого контейнера C++ и о том, откуда он на самом деле берется. Ты угадал. Псевдоним.   -  person Sam Varshavchik    schedule 15.08.2020
comment
Хотя это и не связано, я обнаружил, что boost::typeindex::type_id_with_cvr<decltype(arg) or T>().pretty_name(); полезно узнать, какой тип T и param.   -  person BAKE ZQ    schedule 15.08.2020


Ответы (1)


Использование такого typedef является каноническим способом сделать это и часто используется в стандартной библиотеке. Фактически, все LegacyIterators должны определять value_type, difference_type, reference, pointer и iterator_category. Только тогда можно будет получить универсальный доступ к их характеристикам с помощью std::iterator_traits.

Например:

// a simple forward iterator
struct Iterator {
    using value_type = int;
    using reference = value_type &;
    using pointer = value_type *;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::forward_iterator_tag;

    // LegacyIterators also need two operators to be defined:

    // the result of operator* is unspecified, we can choose it freely
    value_type operator*();
    // operator++ needs to return a reference to self
    Iterator& operator++();
};

// we can now access the traits universally, as can various standard library functions
static_assert (std::is_same_v<std::iterator_traits<Iterator>::value_type, int> );

Таким образом, то, что вы делаете, является правильным способом сделать это, но вы должны придерживаться этих точных имен, чтобы стандартная библиотека могла получить доступ к свойствам вашего итератора. Также я бы рекомендовал использовать using вместо typedef. Он не только имеет = в качестве визуального разделителя, но и универсально применим, например. в отличие от typedef его можно создать по шаблону. См. В чем разница между "typedef" и "using" в С++ 11?

person Jan Schultke    schedule 14.08.2020