如何正确地将const指针从非const迭代器声明为指针

时间:2015-04-09 13:18:04

标签: c++ c++11 const-correctness

背景

我实现了一个模板过滤迭代器。给定任何类型的开始和结束迭代器,此迭代器将遍历范围并跳过一元谓词返回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的

2 个答案:

答案 0 :(得分:3)

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

但我理解 - 有时候你希望const能够传播指针。

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

template<class T>struct tag{using 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 -forcing包装器,强制元素访问通过以上转型。这很难。

通常,如果您希望int*对象为const,则指向对象const,则需要一个强制执行该要求的智能指针。 N4388是一个添加包装器的提议,如果不是为了这个目的,那么它就是标准的。

答案 1 :(得分:2)

您的容器存储int*。您的函数接受“int*的const引用”。这意味着指针指向可变数据。您可以愉快地修改数据,因为您允许它。请注意“constness of degree”之间的区别 - 您无法更改指针指向的内容,但您可以修改它们指向的int。因此,要解决此问题,您的函数必须接受“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;