Why Do Some People Prefer "T Const&" Over "Const T&"

Why do some people prefer T const& over const T&?

I think some people simply prefer to read the declarations from right to left. const applies to the left-hand token, except when there is nothing there and it applies on the right-hand token. Hence const T& involves the "except"-clause and can perhaps be thought more complicated (in reality both should be as easy to understand).

Compare:

const T* p;  (pointer to T that is const)
T const* p; (pointer to const T) //<- arguable more natural to read
T* const p; (const pointer to T)

What is the difference between const int*, const int * const, and int const *?

Read it backwards (as driven by Clockwise/Spiral Rule):

  • int* - pointer to int
  • int const * - pointer to const int
  • int * const - const pointer to int
  • int const * const - const pointer to const int

Now the first const can be on either side of the type so:

  • const int * == int const *
  • const int * const == int const * const

If you want to go really crazy you can do things like this:

  • int ** - pointer to pointer to int
  • int ** const - a const pointer to a pointer to an int
  • int * const * - a pointer to a const pointer to an int
  • int const ** - a pointer to a pointer to a const int
  • int * const * const - a const pointer to a const pointer to an int
  • ...

And to make sure we are clear on the meaning of const:

int a = 5, b = 10, c = 15;

const int* foo; // pointer to constant int.
foo = &a; // assignment to where foo points to.

/* dummy statement*/
*foo = 6; // the value of a can´t get changed through the pointer.

foo = &b; // the pointer foo can be changed.

int *const bar = &c; // constant pointer to int
// note, you actually need to set the pointer
// here because you can't change it later ;)

*bar = 16; // the value of c can be changed through the pointer.

/* dummy statement*/
bar = &a; // not possible because bar is a constant pointer.

foo is a variable pointer to a constant integer. This lets you change what you point to but not the value that you point to. Most often this is seen with C-style strings where you have a pointer to a const char. You may change which string you point to but you can't change the content of these strings. This is important when the string itself is in the data segment of a program and shouldn't be changed.

bar is a constant or fixed pointer to a value that can be changed. This is like a reference without the extra syntactic sugar. Because of this fact, usually you would use a reference where you would use a T* const pointer unless you need to allow NULL pointers.

Prefer constexpr over const

In short, yes, you should prefer constexpr as long as your expression can be and should be evaluated at compile time.

See this question for more detail:
Difference between `constexpr` and `const`

Why can we use `std::move` on a `const` object?

struct strange {
mutable size_t count = 0;
strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

here we see a use of std::move on a T const. It returns a T const&&. We have a move constructor for strange that takes exactly this type.

And it is called.

Now, it is true that this strange type is more rare than the bugs your proposal would fix.

But, on the other hand, the existing std::move works better in generic code, where you don't know if the type you are working with is a T or a T const.

Are the days of passing const std::string & as a parameter over?

The reason Herb said what he said is because of cases like this.

Let's say I have function A which calls function B, which calls function C. And A passes a string through B and into C. A does not know or care about C; all A knows about is B. That is, C is an implementation detail of B.

Let's say that A is defined as follows:

void A()
{
B("value");
}

If B and C take the string by const&, then it looks something like this:

void B(const std::string &str)
{
C(str);
}

void C(const std::string &str)
{
//Do something with `str`. Does not store it.
}

All well and good. You're just passing pointers around, no copying, no moving, everyone's happy. C takes a const& because it doesn't store the string. It simply uses it.

Now, I want to make one simple change: C needs to store the string somewhere.

void C(const std::string &str)
{
//Do something with `str`.
m_str = str;
}

Hello, copy constructor and potential memory allocation (ignore the Short String Optimization (SSO)). C++11's move semantics are supposed to make it possible to remove needless copy-constructing, right? And A passes a temporary; there's no reason why C should have to copy the data. It should just abscond with what was given to it.

Except it can't. Because it takes a const&.

If I change C to take its parameter by value, that just causes B to do the copy into that parameter; I gain nothing.

So if I had just passed str by value through all of the functions, relying on std::move to shuffle the data around, we wouldn't have this problem. If someone wants to hold on to it, they can. If they don't, oh well.

Is it more expensive? Yes; moving into a value is more expensive than using references. Is it less expensive than the copy? Not for small strings with SSO. Is it worth doing?

It depends on your use case. How much do you hate memory allocations?

Use of 'const' for function parameters

The reason is that const for the parameter only applies locally within the function, since it is working on a copy of the data. This means the function signature is really the same anyways. It's probably bad style to do this a lot though.

I personally tend to not use const except for reference and pointer parameters. For copied objects it doesn't really matter, although it can be safer as it signals intent within the function. It's really a judgement call. I do tend to use const_iterator though when looping on something and I don't intend on modifying it, so I guess to each his own, as long as const correctness for reference types is rigorously maintained.

Cannot convert from const_Ty2* to ValueType*

Your function is const and you're correctly using a const_iterator. This means that it->second is also const. When you do &it->second that becomes a const pointer which cannot be implicitly converted to a non-const pointer (such a conversion discards the const qualifier).

It's unclear why you want a non-const pointer to an internal value. I have to presume it's an error. You should change the return type to const ValueType*.

Regarding the edit you just made to your question:

radixTree.insert(key, value);

The message is telling you that the function arguments are wrong. Check the documentation for the insert function. You'll find that it requires a value_type which is a std::pair<const Key, T>. The useful part of the error is here:

cannot convert argument 1 from 'std::string' to 'std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const std::string,ValueType>>>>

There it is trying to match a call that has two arguments, which the overload resolution is trying to resolve it to insert(const_iterator hint, const value_type &value). That should tell you something is wrong.

Try this:

radixTree.insert(std::make_pair(key, value));


Related Topics



Leave a reply



Submit