C++ 如何将模板参数T转换为常量T?
假设我有一个下面的类C++ 如何将模板参数T转换为常量T?,c++,template-argument-deduction,C++,Template Argument Deduction,假设我有一个下面的类 template <typename T> struct Node { T value; Node* next; }; 模板 结构节点{T值;节点*next;}; 通常需要编写类似的代码(现在假设Sometype是std::string,尽管我认为这并不重要) Node Node=Node{someValue,someNodePtr}; ... Node constNode=Node;//编译错误 解决方法之一是定义显式转换运算符: template &l
template <typename T>
struct Node { T value; Node* next; };
模板
结构节点{T值;节点*next;};
通常需要编写类似的代码(现在假设Sometype是std::string,尽管我认为这并不重要)
Node Node=Node{someValue,someNodePtr};
...
Node constNode=Node;//编译错误
解决方法之一是定义显式转换运算符:
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)};
}
};
模板
结构体类型
{
T值;
节点*下一步;
运算符节点()常量{
返回节点{value,reinterpret_cast(next)};
}
};
有没有更好的“适当”的方法?
1.一般来说,除了显式定义转换运算符外,允许将SomeType转换为SomeType的正确方法是什么?(不仅仅在我的例子中)。
2.如果定义转换运算符是必要的,那么重新解释是正确的方法吗?还是有“更干净”的方法
编辑:答案和评论非常有用。我决定现在就提供更多的上下文。我的问题不是实现const_迭代器本身(我认为我知道如何实现),而是如何为迭代器和const_迭代器使用相同的模板。这就是我的意思
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;
};
模板
结构迭代器
{
迭代器(Node*\u Node):Node{{u Node}{}
T运算符*(){return node->value;}//仅用于迭代器(&O)
const T&operator*()const{return node->value;}//迭代器需要这两者
//使常量迭代器可用
迭代器和运算符++(){node=node->next;返回*this;}
迭代器运算符++(int){auto result=iterator{node};node=node->next;返回结果;}
布尔运算符==(常量迭代器和其他){return node==other.node;}
布尔运算符!=(常量迭代器和其他){return Node!=other.Node;}
私人:
节点*节点;
};
实现const_迭代器本质上是相同的,除了T&operator*(){return node->value;}
最初的解决方案是只编写两个包装器类,一个带有T&运算符*(),另一个没有。或者使用继承,迭代器派生自const_iterator(这可能是一个很好的解决方案,并且有一个优势-我们不需要为迭代器重写比较运算符,并且可以将迭代器与const_iterator进行比较-这通常是有意义的-因为我们检查它们是否都指向同一节点)
然而,我很好奇如何在不继承或不输入两次相同代码的情况下编写此代码。基本上,我认为需要一些条件模板生成——只为迭代器而不是常量迭代器生成方法T&operator*(){return node->value;}。正确的方法是什么?如果const_iterator将节点*视为节点*,它几乎解决了我的问题。也许您需要另一个类,如下所示:
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) {
}
};
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>>;
有没有更好的“适当”的方法
<> P>既然你的解决方案都有怪异行为,而且也被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
}
如您所见,只复制一个数据,而不复制其余节点
你能做什么 在评论中,您提到您想要实现一个常量迭代器。这可以在不更改数据结构的情况下完成:
// 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;
};
由于包含指向常量节点的指针,因此无法在节点内部更改值。表达式node->value
产生一个T常量&
由于这些节点只是为了实现列表
,因此我假设它们被完全抽象掉了,并且从未向列表的用户公开过
如果是这样,那么您永远不必转换节点,并且在列表及其迭代器的实现中对指向常量的指针进行操作
要重用同一迭代器,我将执行以下操作:
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) {
}
};
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>>;
模板
结构迭代器{
使用reference=T&;
使用node_指针=node*;
};
模板
结构常量迭代器基{
使用reference=T const&;
使用node_指针=node const*;
};
模板
使用select_迭代器_base=std::conditional\u 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>;
模板
结构列表\u基本\u迭代器:选择\u迭代器\u基{
自动运算符*()->typename选择迭代器基::引用{
返回节点->值;
}
auto operator++()->list_basic_iterator&{
节点=节点->下一步;
归还*这个;
}
私人:
typename选择\迭代器\基::节点\ ptr节点;
};
使用迭代器=列表\基本\迭代器;
使用常量迭代器=列表基本迭代器;
您的重新解释强制转换是未定义的行为,因为它打破了严格的别名。这种情况是怎么发生的?它通常不会发生Node
和Node
是两种不相关的类型,它们也可以命名为P
和T
。为什么需要节点而不是常量节点
?您是否考虑了一个用例?@Nelfeal-例如实现const_迭代器。或者,如果我编写了一个版本的智能指针,并且希望允许将SmartPointer转换为SmartPointer,因为T*转换为const T*是一个有效的转换。我认为应该使用pointer=std::conditional_T和Reference=std::conditional_T。我认为T&const不是有效的类型。@RazielMagiusconst T*
和T const*
是相同的T*const
是一个常量指针T&const
确实是无效的,因为引用总是常量。@RazielMagius您感到困惑。看看这些错误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;
};
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>;