Reputation: 7953
I have two and a half closely related questions. Given an STL iterator-type passed as a template parameter:
enable_if
s for instance) that this type corresponds to a non-const iterator?Where does this question come from:
I wrote a small class to facilitate arithmetic/relational/algebraic operations on vectors (by vector I mean 1d fixed-size data, not the STL vectors). Instead of imposing a specific data container, I've defined an interface and derived several possible containers that are basically "wrapping" various ways of storing data. One of these containers is a wrapper for STL-iterators, and I'm having some troubles with it.
Upvotes: 8
Views: 440
Reputation: 171127
For 1), you could do something like this:
std::is_const<
typename std::remove_reference<
typename std::iterator_traits<Iterator>::reference
>::type
>::value
Or this:
std::is_const<
typename std::remove_reference<
decltype(*iterator)
>::type
>::value
You can use these predicates to pass to std::enable_if
to implement 2).
NOTE: As pointed out by R. Martinho Fernandes in the comments, these predicates will fail if the iterator in question uses a different type than plain references for its reference
trait (such as std::vector<bool>::const_iterator
does).
Upvotes: 3
Reputation: 283644
You could use SFINAE on
decltype( **(T*)0 = std::move(**(T*)0) )
or (Xeo's preference)
decltype( *declval<T&>() = std::move(*declval<T&>()) )
which checks whether dereferencing the iterator gives you something assignable. Not perfect, if the element type of the collection isn't assignable, but then what good would it be to have a non-const_iterator
anyway?
Don't test for const_iterator
, test for the operation your algorithm actually needs.
Upvotes: 2
Reputation: 126432
Question 1:
You could use the following type trait:
template<typename T, typename = void>
struct is_const_iterator : std::false_type { };
template<typename T>
struct is_const_iterator<T,
typename std::enable_if<
std::is_const<
typename std::remove_pointer<
typename std::iterator_traits<T>::pointer
>::type
>::value
>::type> : std::true_type { };
Here is a demonstration:
#include <type_traits>
#include <iterator>
#include <list>
#include <vector>
template<typename T, typename = void>
struct is_const_iterator : std::false_type { };
template<typename T>
struct is_const_iterator<T,
typename std::enable_if<
std::is_const<
typename std::remove_pointer<
typename std::iterator_traits<T>::pointer
>::type
>::value
>::type> : std::true_type { };
int main()
{
typedef std::list<int>::iterator LI;
typedef std::list<int>::const_iterator CLI;
static_assert(is_const_iterator<LI>::value, "!"); // Fires
static_assert(is_const_iterator<CLI>::value, "!"); // Does not fire
typedef std::vector<int>::iterator VI;
typedef std::vector<int>::const_iterator CVI;
static_assert(is_const_iterator<VI>::value, "!"); // Fires
static_assert(is_const_iterator<CVI>::value, "!"); // Does not fire
}
And here is a live example.
Question 2:
With the above type trait, this becomes simple. Suppose you have a function template foo()
that you want to constrain so that it accepts only non-const
iterators:
template<typename It,
typename std::enable_if<!is_const_iterator<It>::value>::type* = nullptr>
void foo(It i)
{
// Does something with i...
}
And a simple demonstration program:
int main()
{
std::vector<int> v;
foo(v.begin()); // OK
foo(v.cbegin()); // ERROR!
}
And here is a live example.
Upvotes: 6