13

I'm doing this:

const int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
const auto foo = cbegin(arr);
const typename iterator_traits<decltype(foo)>::value_type bar = 1;

I would have expected bar to have the type int. But instead I'm getting an error:

error C2039: value_type: is not a member of std::iterator_traits<_Ty *const >

Is this a problem with the const do I need to strip that or something?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • I generally use [`std::decay`](https://en.cppreference.com/w/cpp/types/decay) for this. Though, I'm reluctant to post this as an answer because I'm not entirely comfortable with why it seems to fix all of these problems. So it might not be the right solution. You would write `const typename std::iterator_traits>::value_type bar = 1;`. – François Andrieux Jan 09 '19 at 15:29
  • @FrançoisAndrieux Yeah, I was hoping I could make this work for either a `vector` or an array... `decay` *would* still work there though... so maybe that's best? – Jonathan Mee Jan 09 '19 at 15:32
  • @JonathanMee It does work for non-pointer iterator types as well. In my experience it fixes this kind of problem every time and magically works. Though again, the fact that it seems like magic to me leaves me nervous about recommending it. – François Andrieux Jan 09 '19 at 15:33
  • @FantasticMrFox That change is intended to deal with `volatile T*`. It does not add a specialization for `T* const`. – xskxzr Jan 10 '19 at 02:53

3 Answers3

19

The issue here is with the line

const auto foo = cbegin(arr);

cbegin(arr) is going to return a int const * (pointer to const int) so applying const to that with const auto foo means foo is a int const * const (const pointer to const int)

std::iterator_traits is only specialized for a T* or T const* so giving it a T* const fails since there is no valid specialization.

You can fix this by removing the constness in the declaration of bar with

const typename std::iterator_traits<std::remove_cv_t<decltype(foo)>>::value_type

or you can change foo to

auto foo = std::cbegin(arr);

if you are okay with it not being const.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    It “fixes” the problem by weakening the type contract of `foo`. This is hardly a good solution: `foo` *should* be declared `const`. Don’t remove that! – Konrad Rudolph Jan 09 '19 at 15:37
  • 1
    I agree with @KonradRudolph that it would be best to leave `foo` as `const`. I feel like it would be better to instead fix the template argument given to `iterator_traits`. – François Andrieux Jan 09 '19 at 15:40
  • 1
    @KonradRudolph I've updated the answer but it really depends on what the OP wants to do with it. You can normally reassign `const_iterators` from containers so removing the `const` makes it behave the same. – NathanOliver Jan 09 '19 at 15:47
8

Indeed the const is problematic, you do basically:

std::iterator_traits<const int* const>::value_type // incorrect due to the last const

You might fix it by changing it to

std::iterator_traits<const int*>::value_type // Correct

You might use std::decay or std::remove_cv for that:

const typename std::iterator_traits<std::remove_cv_t<decltype(foo)>>::value_type

(or drop const from foo if relevant).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • It's worth pointing out [@FantasticMrFox's comment](https://stackoverflow.com/questions/54113330/why-cant-i-get-value-type-from-iterator-traits/54114989#comment95059225_54113330) that the `remove_cv_t` happens automatically in C++20. So I'd say this is indeed the preferred solution. – Jonathan Mee Jan 09 '19 at 18:59
1

Declaring a const qualified iterator const auto foo = cbegin(arr); is questionable. What use do you have for an iterator on which you cannot apply operator++()? Also, the Iterator requirement requires the type int const *const to be Copy Assignable; as such, the variable foo does not satisfy the Iterator requirement. So strictly speaking, foo is not an Iterator.

  • I mean `next` still works on it fine? It's the same argument against `const char* const` but in some cases this compiles into more optimized code than a `const char*` – Jonathan Mee Jan 09 '19 at 18:25
  • @JonathanMee `std::next` requires an argument that satisfy [LegacyInputIterator](https://en.cppreference.com/w/cpp/named_req/InputIterator), which requires the type to be CopyAssignable through the LegacyIterator requirement. An Iterator type is intrinsically not const qualifiable. – Julien Villemure-Fréchette Jan 09 '19 at 21:04
  • @JulienVillrmure-Fréchette I assume from your statement: "An Iterator type is intrinsically not const qualifiable." That you mean that the only way to modify a constant iterator would be to operate on a copy of it? And that generally the point of an iterator is modification? – Jonathan Mee Jan 09 '19 at 21:47
  • Trivial example to a meaningful iterator constant: the end of a range. Though I also don't see a use-case for const cbegin... – stefan Jan 10 '19 at 05:35