如何启用转换模板参数 T 到 const T?

How to enable conversion template argument T to const T?

假设我有一个关注者class

template <typename T>
struct Node { T value; Node* next; };

通常需要编写与此类似的代码(假设 Sometype 现在是 std::string,尽管我认为这不重要)。

Node<SomeType> node = Node{ someValue, someNodePtr };
...
Node <const SomeType> constNode = node; // compile error

一种解决方法是定义显式转换运算符:

template <typename T>
struct Node
{
    T value;
    Node* next;
    operator Node<const T>() const { 
        return Node<const T>{value, reinterpret_cast<Node<const T>* >(next)};
    }
};

有更好的 "proper" 方法吗? 1. 通常,除了显式定义转换运算符之外,允许将 SomeType 转换为 SomeType 的正确方法是什么? (不仅在我的例子中)。 2. 如果需要定义转换运算符,reinterpret_cast 是正确的做法吗?或者有"cleaner"种方法?

编辑:答案和评论非常有帮助。我决定现在提供更多背景信息。我的问题不在于实现 const_iterator 本身(我想我知道该怎么做),而是如何为迭代器和 const_iterator 使用相同的模板。这就是我的意思

template <typename T>
struct iterator
{
    iterator(Node<T>* _node) : node{ _node } {}
    T& operator*() { return node->value; } // for iterator only
    const T& operator*() const { return node->value; } // we need both for iterator 
                                                       // for const iterator to be usable

    iterator& operator++() { node = node->next; return *this; }
    iterator operator++(int) { auto result = iterator{ node }; node = node->next; return result; }

    bool operator==(const iterator& other) { return node == other.node; }
    bool operator!=(const iterator& other) { return Node != other.node; }

private:
    Node<T>* node;
};

实现 const_iterator 本质上是相同的,除了 T& operator*() { return node->value; }.

最初的解决方案是编写两个包装器 classes,一个带有 T& operator*() ,另一个不带。或者使用继承,迭代器从 const_iterator 派生(这可能是一个很好的解决方案并且有一个优势 - 我们不需要为迭代器重写比较运算符并且可以将迭代器与 const_iterator 进行比较 - 这最常见有道理 - 因为我们检查它们都指向同一个节点)。

但是,我很好奇如何在不继承或两次键入相同代码的情况下编写此代码。基本上,我认为需要生成一些条件模板——使用方法 T& operator*() { return node->value; } 只为迭代器而不是 const_iterator 生成。正确的做法是什么?如果const_iterator把Node*当成Node*,差不多就解决了我的问题。

也许你想要另一个 class,像这样:

template<typename T>
struct NodeView
{
    T const& value; // Reference or not (if you can make a copy)
    Node<T>* next;

    NodeView(Node<T> const& node) :
    value(node.value), next(node.next) {
    }
};

Demo

但是,如果您谈论的是迭代器或奇特的指针(正如您在评论中提到的那样),那么使用附加模板参数和一些 std::conditional:

就很容易做到
template<typename T, bool C = false>
class Iterator {
public:
    using Pointer = std::conditional_t<C, T const*, T*>;
    using Reference = std::conditional_t<C, T const&, T&>;

    Iterator(Pointer element) :
    element(element) {
    }
    Iterator(Iterator<T, false> const& other) :
    element(other.element) {
    }

    auto operator*() -> Reference {
        return *element;
    }

private:
    Pointer element;

    friend Iterator<T, !C>;
};

Demo

Is there a better, "proper" way to do it?

必须存在,因为您的解决方案既有奇怪的行为,也按照 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

如您所见,您只复制了一个数据,而没有复制其余节点。


你能做什么?

在您提到的评论中,您想要实现一个 const 迭代器。这可以在不更改数据结构的情况下完成:

// 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>;