Reputation: 5931
I'm implementing a template filtering iterator. Given a start and end iterator of any type, this iterator will iterate over the range and skip over any elements for which a unary predicate returns false. Naturally I want this unary predicate to always have a const argument as to avoid modification of the backing container by the predicate.
The backing iterator can be an iterator for any type and container. It can be basic types, pointers, references, classes. Anything really.
I run into a problem where I'm unable to declare a std::function
to have the correct, const
declared argument based on the template argument iterator. I have distilled a minimal code example that illustrates the problem.
#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;
}
I'm expecting a compile error on the lamda because int*
should be const int*
but both GCC 4.8.1 and VS2013 allow it and happily modify what I thought was a going to be const.
Upvotes: 2
Views: 808
Reputation: 275385
A container of pointers is not modified when you modify what the pointers point to: the container owns the pointers, not the pointed-to.
But I understand -- sometimes you want const
ness to propogate down pointers.
This is a bit of template metaprogramming which should take any non-const
pointer and make it const
, as well as everything else becomes as const
as it can. It operates recursively, handling references (r and l value) and references to pointers to pointers to pointers to pointers, with or without const
or volatile
modifiers pretty much anywhere.
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>{};
which is a lot of verbage. The reason why it is so verbose is that there is no easy way to match on "type modifiers" (pointer, const, volatile, reference etc), so you end up having to be really specific and verbose.
But it gives you a clean way to do it at point of use:
typedef std::vector<int*> vec_type;
typedef std::function<void(make_very_const_t<vec_type::iterator::value_type&>)> func_type;
which spews const
all over everything in a way that should be implicitly convertible to.
Now, even this isn't fully effective. A std::vector< std::vector<int*> >
will not protect the pointed to inner int
from being modified. The above relies on the ability to implicitly convert to the more const
case -- so to pull this off, we'd have to write a const-forcing wrapper around a nearly arbitrary container that enforces element access to go through the above transformation. This is difficult.
In general, if you want an int*
where the object being const
makes the pointed to object const
, you need a smart pointer that enforces that requirement. N4388 is a proposal to add a wrapper that does that to the standard, if not exactly for this purpose.
Upvotes: 3
Reputation: 15814
Your container stores int*
s. Your function accepts "const reference to int*
". This means that the pointer points to the mutable data. The data can be happily modified because you allowed it. Note the distinction between the "degrees of constness" - you can't change what the pointers point to, but you can modify the int
s they point to. So, to fix this problem, either your function must accept "const reference to const int*
" or const int*
.
typedef std::function<void(const int*)> func_type;
...or, if you want it slightly more generic (see Yakk's answer for even more generic solution):
#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;
Upvotes: 2