如何正确地声明从非常量迭代器到指针的常量指针

How to properly declare a const pointer from non-const iterator to pointer

本文关键字:常量 指针 迭代器 非常 正确地 声明      更新时间:2023-10-16

背景

我正在实现一个模板过滤迭代器。给定任何类型的开始和结束迭代器,该迭代器将在范围内迭代,并跳过一元谓词返回false的任何元素。当然,我希望这个一元谓词总是有一个常量参数,以避免谓词修改支持容器。

支持迭代器可以是任何类型和容器的迭代器。它可以是基本类型、指针、引用、类。真的什么都可以。

我遇到了一个问题,我无法声明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都允许它,并很高兴地修改了我认为会是常量的内容。

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

但我理解——有时你想让constness按比例向下指针。

这是一个模板元编程,它应该接受任何非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>{};

实例

这是大量的措辞。它之所以如此详细,是因为没有简单的方法来匹配"类型修饰符"(指针、常量、volatile、引用等),所以最终必须非常具体和详细。

但它为您在使用时提供了一种干净的方法:

typedef std::vector<int*> vec_type;
typedef std::function<void(make_very_const_t<vec_type::iterator::value_type&>)> func_type;

它以一种应该隐式转换为.的方式在所有事物上喷出CCD_ 11

现在,即使这样也不能完全有效。std::vector< std::vector<int*> >不会保护指向内部的int不被修改。以上依赖于隐式转换为更const的情况的能力——因此,为了实现这一点,我们必须在一个几乎任意的容器周围编写一个const强制包装器,该容器强制元素访问以完成上述转换。这很难。

通常,如果您想要一个int*,其中作为const的对象成为指向对象const,则需要一个强制执行该要求的智能指针。N4388建议在标准中添加一个这样做的包装器,如果不是完全为了这个目的的话。

您的容器存储int*s。您的函数接受"对int*的常量引用"。这意味着指针指向可变数据。数据可以被愉快地修改,因为你允许它。注意"constness的程度"之间的区别-你不能改变指针指向什么,但你可以修改它们指向的ints。因此,要解决这个问题,你的函数必须接受"const-reference toconst 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;