如何正确声明从非常量迭代器到指针的 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*&amp; x)x = 0; );,它将失败。 @molbdnilo 我怀疑发生了类似的事情。但是,如果用户忘记添加&amp;(如果您不阅读文档,这并不明显),那么代码将愉快地修改后备容器。您对如何解决这个问题有什么建议吗? [](int* x)x = 0; - 为什么你认为这是在修改容器? 【参考方案1】:

当您修改指针指向的内容时,指针容器不会被修改:容器拥有指针,而不是指向的对象。

但我理解 - 有时您希望 constness 传播向下指针。

这是一个模板元编程,它应该采用任何非const 指针并将其设为const,以及其他所有内容尽可能变为const。它以递归方式运行,处理引用(r 和 l 值)以及对指针的引用、指向指针的指针、指向指针的指针,几乎可以在任何地方使用或不使用 constvolatile 修饰符。

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&lt; std::vector&lt;int*&gt; &gt; 不会保护指向内部的int 不被修改。以上依赖于隐式转换为更多const 情况的能力——因此,为了实现这一目标,我们必须围绕一个几乎任意的容器编写一个强制 const 的包装器,该容器强制元素访问通过上述转换.这很难。

一般来说,如果你想要一个int*,其中const 的对象指向const,你需要一个智能指针来强制执行该要求。 N4388 提议在标准中添加一个执行此操作的包装器,如果不是完全出于此目的。

【讨论】:

您的回答正是我所需要的。我只是想了解你的模板(我用不同的技术做了something similar,但结果是一样的)。 @EmilyL。 tag 是我懒惰(我可以用 using type=blah; 替换 :tag&lt;blah&gt;。其他一切都只是“匹配模​​式,做任何事情”模式。make_very_const_t 是一个别名,可以摆脱 typename 废话。模式是基本上只添加const 的情况是不正确的:排列以便它们之间没有歧义。 template &lt;typename T&gt; struct make_very_const&lt;const T&gt; 是当 T 为 const 时的某种特化?没见过这个结构,叫什么? @emilyl。只是专精吗?我不知道它的特殊名称。【参考方案2】:

您的容器存储int*s。您的函数接受“对int* 的常量引用”。这意味着指针指向可变数据。数据可以愉快地修改,因为您允许它。请注意“常量度”之间的区别 - 您无法更改指针指向的内容,但您可以修改它们指向的 ints。因此,要解决此问题,您的函数必须接受“对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 指针的主要内容,如果未能解决你的问题,请参考以下文章

如何防止从非常量访问指针中过滤指针向量的函数?

const 正确性:如何在保持函数 const 的同时从迭代器字段中获取元素?

顶层const和底层const

为啥 const 方法不能返回非常量引用?

C++Primer 第七章

C++ NULL 指针和 const 正确性