如何正确声明从非常量迭代器到指针的 const 指针
Posted
技术标签:
【中文标题】如何正确声明从非常量迭代器到指针的 const 指针【英文标题】:How to properly declare a const pointer from non-const iterator to pointer 【发布时间】:2015-04-09 13:18:04 【问题描述】:背景
我正在实现一个模板过滤迭代器。给定任何类型的开始和结束迭代器,此迭代器将遍历范围并跳过一元谓词返回 false 的任何元素。自然,我希望这个一元谓词始终有一个 const 参数,以避免谓词修改后备容器。
支持迭代器可以是任何类型和容器的迭代器。它可以是基本类型、指针、引用、类。真的什么都行。
我遇到了一个问题,我无法根据模板参数迭代器声明 std::function
以获得正确的 const
声明参数。我提炼了一个说明问题的最小代码示例。
代码
#include <vector>
#include <functional>
typedef std::vector<int*> vec_type;
typedef std::function<void(const vec_type::iterator::value_type&)> func_type;
void foo(vec_type& a, func_type f)
for (auto it = a.begin(); it != a.end(); ++it)
f(*it);
int main(int, char**)
vec_type v;
int a = 3;
int b = 4;
v.push_back(&a);
v.push_back(&b);
foo(v, [](int* x)*x = 0; );
return 0;
我预计 lamda 上会出现编译错误,因为 int*
应该是 const int*
但 GCC 4.8.1 和 VS2013 都允许它并愉快地修改我认为将是 const 的内容。
【问题讨论】:
使用vec_type::const_iterator
而不是const vec_type::iterator
@WojciechFrohmberg 在我的情况下是不可接受的。我会添加一些背景。
func_type
的参数类型是“const reference to int*
”,而不是“reference to const int*
”。如果您将呼叫更改为foo(v, [](int*& x)x = 0; );
,它将失败。
@molbdnilo 我怀疑发生了类似的事情。但是,如果用户忘记添加&
(如果您不阅读文档,这并不明显),那么代码将愉快地修改后备容器。您对如何解决这个问题有什么建议吗?
[](int* x)x = 0;
- 为什么你认为这是在修改容器?
【参考方案1】:
当您修改指针指向的内容时,指针容器不会被修改:容器拥有指针,而不是指向的对象。
但我理解 - 有时您希望 const
ness 传播向下指针。
这是一个模板元编程,它应该采用任何非const
指针并将其设为const
,以及其他所有内容尽可能变为const
。它以递归方式运行,处理引用(r 和 l 值)以及对指针的引用、指向指针的指针、指向指针的指针,几乎可以在任何地方使用或不使用 const
或 volatile
修饰符。
template<class T>struct tagusing type=T;;
template<class X>
struct make_very_const:tag<const X> ;
template<class X>
using make_very_const_t=typename make_very_const<X>::type;
// makes code below easier to write (namely, the pointer code):
template<class X>
struct make_very_const<const X>:tag<const make_very_const_t<X>> ;
template<class X>
struct make_very_const<volatile X>:tag<const volatile make_very_const_t<X>> ;
template<class X>
struct make_very_const<const volatile X>:tag<const volatile make_very_const_t<X>> ;
// references:
template<class X>
struct make_very_const<X&>:tag<make_very_const_t<X>&>;
template<class X>
struct make_very_const<X&&>:tag<make_very_const_t<X>&&>;
// pointers:
template<class X>
struct make_very_const<X*>:tag<make_very_const_t<X>*const>;
// std::reference_wrapper:
template<class X>
struct make_very_const<std::reference_wrapper<X>>:tag<std::reference_wrapper<make_very_const_t<X>>const>;
live example
这是一大堆废话。它如此冗长的原因是没有简单的方法来匹配“类型修饰符”(指针、常量、易失性、引用等),因此您最终必须非常具体和冗长。
但它为您在使用时提供了一种干净的方式:
typedef std::vector<int*> vec_type;
typedef std::function<void(make_very_const_t<vec_type::iterator::value_type&>)> func_type;
它以一种可以隐式转换为的方式在所有东西上喷出const
。
现在,即使这样也不是完全有效。 std::vector< std::vector<int*> >
不会保护指向内部的int
不被修改。以上依赖于隐式转换为更多const
情况的能力——因此,为了实现这一目标,我们必须围绕一个几乎任意的容器编写一个强制 const 的包装器,该容器强制元素访问通过上述转换.这很难。
一般来说,如果你想要一个int*
,其中const
的对象指向const
,你需要一个智能指针来强制执行该要求。 N4388 提议在标准中添加一个执行此操作的包装器,如果不是完全出于此目的。
【讨论】:
您的回答正是我所需要的。我只是想了解你的模板(我用不同的技术做了something similar,但结果是一样的)。 @EmilyL。tag
是我懒惰(我可以用 using type=blah;
替换 :tag<blah>
。其他一切都只是“匹配模式,做任何事情”模式。make_very_const_t
是一个别名,可以摆脱 typename
废话。模式是基本上只添加const
的情况是不正确的:排列以便它们之间没有歧义。
template <typename T> struct make_very_const<const T>
是当 T 为 const 时的某种特化?没见过这个结构,叫什么?
@emilyl。只是专精吗?我不知道它的特殊名称。【参考方案2】:
您的容器存储int*
s。您的函数接受“对int*
的常量引用”。这意味着指针指向可变数据。数据可以愉快地修改,因为您允许它。请注意“常量度”之间的区别 - 您无法更改指针指向的内容,但您可以修改它们指向的 int
s。因此,要解决此问题,您的函数必须接受“对const int*
的常量引用”或const int*
。
typedef std::function<void(const int*)> func_type;
...或者,如果您希望它更通用(有关更通用的解决方案,请参阅 Yakk 的答案):
#include <type_traits>
typedef std::vector<int*> vec_type;
typedef
std::add_pointer<
std::add_const<
std::remove_pointer<
vec_type::iterator::value_type
>::type
>::type
>::type value_t;
typedef std::function<void(const value_t&)> func_type;
【讨论】:
以上是关于如何正确声明从非常量迭代器到指针的 const 指针的主要内容,如果未能解决你的问题,请参考以下文章