5

TL;DR

Given the following type:

struct A
{
    std::vector<std::string> vec;

    using reference = std::iterator_traits<decltype(vec)::iterator>::reference;
    using const_reference = const reference;
};

Why is reference == const_reference? Why is the const qualifier dropped in the second type alias?

See the example on godbold which shouldn't compile.

Details

I have a templated class that takes a bunch of iterators (-types) as template arguments. From these iterators I need to deduce the reference and const reference type because I have some member functions like:

struct A
{
    std::vector<std::string> vec;

    using reference = std::iterator_traits<decltype(vec)::iterator>::reference;
    using const_reference = const reference;

    const_reference foo() const
    {
        return vec[0];
    }
};

By dropping the const qualifier, I'm effectively returning a reference in foo which is illegal because it's a const member function and so the compiler throws.

Community
  • 1
  • 1
Timo
  • 9,269
  • 2
  • 28
  • 58
  • 2
    `reference` is `string&`. `const reference` is actually `string& const` – Passer By May 22 '18 at 14:42
  • The compiler gives you a pretty explicit warning actually: *"'const' qualifier on reference type 'A::reference' (aka ...) has no effect [-Wignored-qualifiers]"* – Holt May 22 '18 at 14:49
  • @Peter But `iterator_traits` has no `const_reference` type. – Timo May 22 '18 at 14:50
  • @Holt Yes I saw that warning but I didn't know *why* it popped up. – Timo May 22 '18 at 14:58

2 Answers2

8

It is dropped. What we call a "const reference" is really a reference to const - const int&.

Having an int& and adding const would make that int& const. But such a const is dropped.

Your problem is similar to the difference between const int* and int* const. Except that for references, int& and int& const is the same type - the const is ignored.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 1
    I find it difficult to believe there is no dupe somewhere. – Passer By May 22 '18 at 14:46
  • There probably is. I started out as a comment, just like you, but it became too large to fit. :-) – Bo Persson May 22 '18 at 14:47
  • Well there is probably a duplicate if you phrase the question correctly. But if you don't know the root of the problem it's kinda hard to phrase it correctly. – Timo May 22 '18 at 14:48
  • @Timo It's obviously not easy to find, but I swear I've seen this question before. – Passer By May 22 '18 at 14:49
  • Would `using const_reference = const std::remove_reference_t&;` be a qualified alternative or are there some pitfals? – Timo May 22 '18 at 14:51
  • 2
    @Timo - That would probably work. I would have used `std::vector::const_reference` and avoided juggling chain saws. – Bo Persson May 22 '18 at 14:54
  • @BoPersson Yeah I would also use that if I could. But as I wrote in the question, I don't know what iterators I get (they could also be native pointers), that's why I have to use `iterator_traits`. – Timo May 22 '18 at 14:55
  • 1
    So I can think of it as, what the OP is doing is `const (T&)` but what they really want is `(const T)&`. The `remove_reference` trick would work because you can extract the `T` from `(T&)`, add const, then put the reference back. – jcai May 22 '18 at 15:16
4

Your problem is west const. West const is bad const east const is best const.

West const is putting the const on the left of the token you want to be const. East const is putting it on the right.

If I told you never to put const on the left of your types, and that const always applies to the thing on its left, look at this:

using const_reference = reference const;

you can probably work out why the const didn't work. After expanding reference naively you get string&const -- here you attempt to apply const to & not string, and a foo &const is not the same as a foo const& -- a foo&const is just a foo& as const cannot apply to the reference itself, but only to the type referred to.

Sure, you say, but that is why I want west const!

West const does the same thing here. It applies to the & not then string and then is discarded. It just does so in a way that is more confusing and harder to grasp intuitively.

To understand const, convert your const to east const. West const is just an exception to the standard east const rule, where if there is no token in the type to the left it applies to the token on the right. The token on the right here is the entire type string& bundled into a type alias (type aliases are not macros). If instead you typed const string& the token on the right would be string and you'd get string const& in sane, east-const style.

East const is best const.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524