Есть ли лучший, «правильный» способ сделать это?
Должен быть, так как ваше решение имеет странное поведение и недействительно в соответствии со стандартом C ++.
Существует правило, называемое строгим псевдонимом, которое определяет, какой тип указателя может использовать псевдоним другого типа. Например, и char*
, и std::byte*
могут быть псевдонимами любого типа, поэтому этот код действителен:
struct A {
// ... whatever
};
int main() {
A a{};
std::string b;
char* aptr = static_cast<void*>(&a); // roughtly equivalent to reinterpret
std::byte* bptr = reintepret_cast<std::byte*>(&b); // static cast to void works too
}
Но вы не можете сделать псевдоним любого типа другим:
double a;
int* b = reinterpret_cast<int*>(&a); // NOT ALLOWED, undefined behavior
В системе типов C ++ каждая реализация типа шаблона - это разные, не связанные между собой типы. Итак, в вашем примере Node<int>
- это совершенно несвязанный тип, отличный от Node<int const>
.
Я также сказал, что ваш код ведет себя очень странно?
Рассмотрим этот код:
struct A {
int n;
A(int _n) : n(_n) { std::cout << "construct " << n << std::endl; }
A(A const&) { std::cout << "copy " << n << std::endl; }
~A() { std::cout << "destruct " << n << std::endl; }
};
Node<A> node1{A{1}};
Node<A> node2{A{2}};
Node<A> node3{A{3}};
node1.next = &node2;
node2.next = &node3;
Node<A const> node_const = node1;
Это выведет следующее:
construct 1
construct 2
construct 3
copy 1
destruct 1
destruct 3
destruct 2
destruct 1
Как видите, вы копируете только одни данные, но не остальные узлы.
Что вы умеете?
В комментариях вы упомянули, что хотите реализовать константный итератор. Это можно сделать без изменения ваших структур данных:
// inside list's scope
struct list_const_iterator {
auto operator*() -> T const& {
return node->value;
}
auto operator++() -> node_const_iterator& {
node = node->next;
return *this;
}
private:
Node const* node;
};
Поскольку вы содержат указатель на постоянный узел, вы не можете изменять value
внутри узла. Выражение node->value
дает T const&
.
Поскольку узлы предназначены только для реализации List
, я предполагаю, что они полностью абстрагированы и никогда не открываются пользователям списка.
Если это так, то вам никогда не придется преобразовывать узел и работать с указателем на константу внутри реализации списка и его итераторов.
Чтобы повторно использовать тот же итератор, я бы сделал что-то вроде этого:
template<typename T>
struct iterator_base {
using reference = T&;
using node_pointer = Node<T>*;
};
template<typename T>
struct const_iterator_base {
using reference = T const&;
using node_pointer = Node<T> const*;
};
template<typename T, bool is_const>
using select_iterator_base = std::conditional_t<is_const, const_iterator_base<T>, iterator_base<T>>;
Затем просто сделайте свой тип итератора параметризованным логическим значением:
template<bool is_const>
struct list_basic_iterator : select_iterator_base<is_const> {
auto operator*() -> typename select_iterator_base<is_const>::reference {
return node->value;
}
auto operator++() -> list_basic_iterator& {
node = node->next;
return *this;
}
private:
typename select_iterator_base<is_const>::node_ptr node;
};
using iterator = list_basic_iterator<false>;
using const_iterator = list_basic_iterator<true>;