I'm reading through Scott Meyers effective C++ book and he recommends for operators making both const and non-const versions, where the non-const just calls the const version will some type stripping to reduce code repetition.
His example is something like this:
const char& CallConstTest::operator[](size_t index) const
{
//Some safety code
return m_buffer[index];
}
char& CallConstTest::operator[](size_t index)
{
return const_cast<char&>(static_cast<const CallConstTest&>(*this)[index]);
}
Now I agree with doing this but I really don't like those casts for readability reasons, so added some template functions that work to try and increase readability as shown below:
template<typename baseType>
const baseType& AddConst(baseType& base){
return static_cast<const baseType&>(base);
}
template<typename baseType>
baseType& RemoveConst(const baseType& base) {
return const_cast<baseType&>(base);
}
char& CallConstTest::operator[](size_t index)
{
return RemoveConst(AddConst(*this)[index]);
}
But then thought to myself, everyone will tell me to use a standard library to do this so it's uniform with any codebases.
I found std::add_const and std::remove_const but the add explicitely says "(unless T is a function, a reference, or already has this cv-qualifier)" so that's a no go, and I cannot get std::remove_const to be happy, probably for similar reasons.
Questions
Is there a commonly used function that everyone already uses for this
If you argue that const_cast<type&>(static_cast<const type&> is actually the correct answer, please explain why
The standard library facilities you named are type traits, but there is std::as_const for AddConst. The other direction has no common name (perhaps because it is unsafe in general).
However, you don’t need the one it could implement if you can define the const version in terms of the other:
const char& CallConstTest::operator[](size_t index) const
{
return const_cast<CallConstTest&>(*this)[index];
}
char& CallConstTest::operator[](size_t index)
{
//Some safety code
return m_buffer[index];
}
The one cast is necessary to select the other overload, but then the return value can be implicitly converted (as it could be in AddConst).
Meyers is right that this sort of const_cast is reasonably idiomatic, although one could certainly find programmers (perhaps ones who had been burned by wanton casting before) who would prefer duplication of at least a small function body.
Related
I built a simple JSON encoder/decoder in C++ (I know there are many excellent solutions for this problem out there; I'm learning the language, and it's simply a challenge to myself, and I'm not about to pretend that my solution is any good).
Initially, I had the following two operator [] overloads:
json& operator[](const std::string& key) {
assert_type_valid(/* "object" type */);
return (*object_data_)[key];
}
json& operator[](size_t index) {
assert_type_valid(/* "array" type */);
return (*array_data_)[index];
}
...
std::unique_ptr<std::unordered_map<std::string, json>> object_data_;
std::unique_ptr<std::vector<json>> array_data_;
And this worked great! I could do something like my_json["someKey"]["someOtherKey"][4]["thirdKey"] to access nested values.
But then, because my project uses a bunch of Windows APIs that make use of both char/std::string and wchar_t/std::wstring, I wanted to make both json and wjson types, so I turned my existing json class and parser into a basic_json<CharType, StringType>.
Now, the same code looks like:
// Using C for CharType and S for StringType for brevity here...
basic_json<C, S>& operator[](const S& key) {
assert_type_valid(/* "object" type */);
return (*object_data_)[key];
}
basic_json<C, S>& operator[](size_t index) {
assert_type_valid(/* "array" type */);
return (*array_data_)[index];
}
...
std::unique_ptr<std::unordered_map<S, basic_json<C, S>>> object_data_;
std::unique_ptr<std::vector<basic_json<C, S>>> array_data_;
Attempting to use either operator [] now results in this error:
error C2666: 'basic_json<char,std::string>::operator []': 2 overloads have similar conversions
could be ...::operator [](size_t)
or ...::operator [](const S &)
or built-in C++ operator[(__int64, const char [8])
I've tried adding a third operator [](const C* key) overload, but that hasn't made a difference.
My actual question: Why has templating caused this?
I'm under the impression that template types are determined at compile time, so presumably the compiler should understand what C and S are and be able to know that I'm not passing in a size_t when I do my_json["someKey"].
Also, I feel like templating has turned the once-pretty code into a huge mess. Is it ever worth simply duplicating a bunch of code in a situation like this? Like having a second parser that just works explicitly with wchar_t and std::wstring?
"Why has templating caused this?"
Because class template members are only instantiated when necessary.
The clue appears to be in the operator[] which you did not mention:
built-in C++ operator[(__int64, const char [8]).
That is to say, the compiler considers 5["someKey"]. Wait, what? Yes, in C++ that is valid. It's the same as "someKey"[5], for compatibility with C.
Now how is that even relevant? Your basic_json<C, S> has an implicit conversion to int and std::string has an implicit conversion from const char [8], so the two overloads are equal.
The fix: remove the implicit conversion to int.
I was watching Bjarne Stroustrup on YouTube and I was trying to figure out why this is considered bad as he said it is C++98 style bad code
void setInt(const unsigned int &i)
void takeaString(const std::string &str)
I mean you are passing a reference to a constant so you save yourself the copy operation and it isnt even using like passing the pointer so it doesnt have to dereference so why is it bad?
In pre-C++11, the general rule of thumb is if you don't modify the argument, pass a built-in type by value and an object of a class or struct by const&, because objects of classes or structs are typically so big that passing by const& pays in terms of performance.
Now that's a fairly arbitrary rule, and you'll also see exceptions, of course (e.g. iterators in the standard library) but it works well in practice and is an established idiom. When you see f(int const &i) or f(std::string s) in some other programmer's code, then you will want to know the reason, and if there's no apparent reason, people will be confused.
In C++11, the story may be different. A lot of people claim that due to new language features (move semantics and rvalue references), passing big objects by value is not a performance problem anymore and may even be faster. Look at this article: "Want Speed? Pass by Value." However, when you look at past Stack Overflow discussions, you will also find that there are experienced programmers opposed to this view.
Personally, I've not made up my mind on this. I consider C++11 too new for me to really judge what's good and bad.
Nevertheless, C++11 is often irrelevant if you have to use a pre-C++11 compiler for whatever reason, so it's important to know the pre-C++11 rules in any case.
Here is when it is good:
bool session_exists(heavy_key_t const& key)
{
// why would we ever want to copy the key if we don't need to
return sessions.find(key)) == sessions.end();
}
This is when passing argument by reference is possibly not so good:
struct session {
heavy_key_t key_;
session(heavy_key_t const& key):
key_(key) // <-- we are taking a copy anyway, why not letting compiler do it
{}
};
And another one that works just fine on values thanks to copy elision optimization and RVO:
template <class T, class Merger>
T merge(T state, T update, Merger const& merger) {
// merger is still by reference, we don't need the copy of it
return merger(std::move(state), std::move(update));
}
I'm implementing a class Aviary, which can store pointers to Bird-objects. Now, I have the following:
class Aviary {
public:
const Bird &operator[](const size_t index) const {
return birds[index];
}
Bird &operator[](const size_t index) {
return birds[index];
}
private:
std::vector<Bird*> birds;
The Bird-objects are stored as pointers in order to avoid object-slicing. However, there is a problem with the operator[]-implementation (Reference to type 'const Bird' could not bind to an lvalue of 'const value_type' (aka 'Bird *const')).
How do I implement the operator[] properly?
Since you store pointers, you should dereference the pointer for return reference.
const Bird &operator[](const size_t index) const {
return *birds[index];
}
Bird &operator[](const size_t index) {
return *birds[index];
}
Side note: use smart pointers, instead of raw pointers.
Two side notes:
The const in a parameter passed by value (const size_t index) is useless and your compiler will ignore it. You can try declaring it with const and removing the const in the implementation: the compiler will correctly consider that your implementation matches the declaration.
The canonical way to implement the non-const version of operator[] is as follows:
As follows
Bird &operator[](size_t index) {
return const_cast<Bird&>(const_cast<const Aviary*>(this)->operator[](index));
}
I know all those const_cast look ugly, but they are both safe and this is the right way to ensure that both versions of operator[] do the same (you just need to maintain the const version from now on), while also making sure that your are not doing any non-const operation in the const version.
Apart from that, the problem with your code is that you are returning pointers, not (references to) the values pointed by them, as Luchian and ForEveR have already pointed out.
You need to dereference:
return *(birds[index]);
birds[index] is a Bird*, so you can't directly return it as a Bird&.
A QList<T *> can't easily be const-correct. Consider the function
void f(QList<T *> list)
{
list[0]->constFunction();
}
I can change f to
void f(QList<const T *> list)
but then I can't do
f(QList<T *>()); //Compile error
anymore, since the compiler can't implicitely cast QList<T *> to QList<const T *>. However, I can explicitely reinterpret-cast the QList as follows:
template <typename T> inline QList<const T *> &constList(const QList<T *> &list)
{
return (QList<const T *> &)list;
}
This enables me to use the constList template function to cast any QList<T *> into a QList<const T *>, as in
f(constList(QList<T *>()));
and it seems to work fine, but is it actually safe to do this?
The casting function you're considering, …
template< class T >
inline QList<T const*>& constList( QList<T*> const& list )
{
return reinterpret_cast< QList<T const*>& >(
const_cast< QList<T*>& >( list )
);
}
… may be practical (probably QList does not change its object representation depending on the const-ness of the element type), but it can break const correctness.
First, because casting away the const-ness of the list itself is not const correct: it allows you to change an originally const list.
But even if that formal argument const is removed, like …
template< class T >
inline QList<T const*>& constList( QList<T*>& list )
{
return reinterpret_cast< QList<T const*>& >(
list
);
}
… there is still a const correctness problem.
The reason is that the list constitutes an additional level of indirection, and with your function is not itself const. Thus, after using your function to get a reference to the list with alleged pointer-to-const elements, you can store in that list a pointer to something that is really const. And then you can use the original list to modify that really const item, bang.
It's the same reason that there is no implicit conversion from T** to T const**.
What you can do without such problems is, with an already const list of pointers to objects, make those pointed to objects const:
template< class T >
inline QList<T const*> const& constList( QList<T*> const& list )
{
return reinterpret_cast< QList<T const*> const& >(
list
);
}
Formally there's still the reinterpret_cast as a potential problem, but anyone specializating the representation of QList on constness of elements would presumably deserve whatever they got. :-)
Cheers & hth.,
Const correctness idea is weak and what you found is one of the many reasons. Constness concept only captures a single bit of semantic (change/don't change) and does so badly and at a quite high syntax cost that sometimes even requires code duplication.
One problem of this concept is that it doesn't scale well by composition: for example I could be interested to pass a function a const vector of non-const objects, or a non-const vector of const objects, or a const vector of const objects and getting the syntax right is expensive... just consider how standard c++ was forced to introduce const_iterator because that is of course different from a const iterator.
Over the years I moved from the zealot position of using const correctness in every place I could to the opposite of using it only where I'm forced to. Experience taught me that code that is not obsessed with const correctness becomes cleaner and that const correctness machinery never really catches (at least for me) any logical error but only errors about the const correctness machinery itself. Unfortunately const correctness is one of the things in C++ that you are forced to use because C++ rules say so and there is no way to just avoid using it.
I know I'm going to be downvoted for this post but my suggestion is to keep a critical eye on whatever you read about C++ on this subject; keep the brain on and judge objectively. Just because would be nice for something being true (e.g. that const correctness helps the programmer) unfortunately it doesn't mean it's really true.
No matter how big is the name that signed the book.
Also remember that if a position requires all the rest of the world to be wrong then it's probably a good idea to be at least a little skeptic: most languages, even those born after C++, don't implement this concept.
So, I realize that const T& and T const& are identical and both mean a reference to a const T. In both cases, the reference is also constant (references cannot be reassigned, unlike pointers). I've observed, in my somewhat limited experience, that most C++ programmers use const T&, but I have come across a few people who use T const&. I use const T& simply because I learned it that way, and so T const& looks a little bit funny to me. What is the reason that you use the variant that you use? Do any of you work at an organization for which the coding standards mandate the use of one variant over the other?
Edit
Based on the answers, it would appear that one reason for choosing between the two is whether you want to read it like the compiler (right-to-left) or like English (left-to-right). If one reads it like the compiler, then "T const&" reads as "& (reference) const (to a constant) T (of type T)". If one reads it like English, from left-to-right, then "const T&" is read as "a constant object of type T in the form of a reference". I prefer to read it like English prose, but I can certainly see the sense in interpreting it the way that the compiler does.
No one has answered the organization or coding standards question, but I strongly suspect that most organizations do not mandate one over the other, although they might strive for consistency.
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)
This will make a difference when you have more then one const/volatile modifiers. Then putting it to the left of the type is still valid but will break the consistency of the whole declaratiion. For example:
T const * const *p;
means that p is a pointer to const pointer to const T and you consistenly read from right to left.
const T * const *p;
means the same but the consistency is lost and you have to remember that leftmost const/volatile is bound to T alone and not T *.
If you find this discussion interesting, you'd probably find this article by Dan Saks interesting. It doesn't directly address your question, but explains why he prefers
VP const foo[];
to
const VP foo[];
It's because given
typedef void *VP;
you could easily be misled into thinking that the second example above means
const void *foo[]; // Wrong, actually: void *const foo[];
but the first example is harder to misinterpret.
My reasoning is as follows:
It does seem to roll off the tongue better if you write "const T&" but when you do that you end up with the ambiguous, "constant T reference." I've seen this cause problems more than once in the understandability of code that allowed someone, even semi-experienced, to misinterpret what something meant or how to declare a more complex type.
I can't think of any example right now but more than once I've answered questions about type declarations and constness where the problem was caused by the habit of using "const T &" instead of "T const &". I used to write it that way as well and when I became a Sr. Developer, someone in charge of mentoring and creating code standards in projects, I found it much easier for entry level developers when I force everyone to use "T const&". I suppose one rather trivial, rookie mistake would be why does this code compile?
const T* t = f();
t = 0; // assignment to const?? - no, it is not the T* that is const, just the T.
When you learn to read it the way that the compiler does it becomes much easier to understand what any given complex type is as well as allowing you to more readily declare complex types when you need to. By complex types I'm talking about things such as:
T const * const &
When you know that the compiler reads right to left, inside to out, what that means becomes quite apparent and when it is necessary to write one you can do so easily: reference to constant pointer to a constant T. Now write the declaration of a "reference to a pointer to a constant pointer to a T". If you simply use left to right notation I think you'll find this quite easy.
In short, though it initially seems unnatural teaching oneself to use right->left grammar ALL the time, instead of only when it is required (because it often is), you'll find it much easier to remember what a line of code means and how to write what you mean. It's sort of along the same lines of why I disallow "," in declarations:
T* ptr1 = 0, ptr2 = 0; // oops!!!
// do it this way please!
T* ptr1 = 0;
T* ptr2 = 0;
Technically it's all the same but when you try to get a bunch of people of varying capacities working on the same thing you'll tend to make sure everyone uses whatever method is the easiest to understand and use. My experience has taught me that "T const&" is that method.
I think is personal preference. There is no difference between the two variants.
Being as code is predominantly English-based, programmers tend to read left to right, so const T& reads naturally to us where the compiler reads it inside out right to left so T const& reads naturally(reference to a const T)
That's because some find it helpful to read the declaration right-to-left.
char const*
const char*
are both pointer to const char.
I used to be a strong advocate of const T& because it does read logically left-to-right (it's a constant T reference). (And probably there's some bias since most code I'd encountered to that point was written that way.)
Over the years I've encountered some corner cases (such as multiple pointer/reference layers, multiple variable declarations, and method pointers) which strain things a little for the reasons other people have already answered. Often introducing additional typedefs help you "unstick" these cases to some extent, but then you have to come up with a name for something even if it's only used once.
The tipping point for me was the introduction of auto in C++11, especially when combined with range-based-for. With that, I've flipped and now strongly prefer T const& (or "reference to constant T", reading right to left) instead. Not only is it more consistent with how the compiler actually parses, it means that when you replace the type with auto, this always ends up at the left, which I think reads better.
Compare:
for (const T& a : list) for (T& a : list)
for (T const& a : list) for (T& a : list)
for (const auto& a : list) for (auto& a : list)
for (auto const& a : list) for (auto& a : list)
Note also the column on the right, where I've added the non-const version of the same. At least for me, the const vs. non-const and auto vs. explicit all seem most consistent in the cases where const appears after T.
But this is a style choice, and as such there is no absolutely correct answer.
If the const and & get far apart, as in
krog::FlamBlott<std::map<HurkleKey,Spleen<int>>
speckleFlams( const krog::Floonage & floon, const std::map<krog::HackleSpoon,std::vector<krog::HeckleObservation<OBSN>>> & obsmap);
krog::FlamFinagleReport
finagleFlams( const krog::Floonage & floon, std::map<krog::HackleSpoon,std::vector<krog::HeckleObservation<OBSN>>> & obsmap)
... then it becomes easy to miss that the first 'obsmap' is a const reference and the second is not.
I use the T const& notation myself, and I recommend it to everyone. The reason is the ergonomics - i find it to reduce eye movement.
#grego already made a great point on how "const" being close to "&" prevents bugs. Usualy when I find "&" I want to know if it is const or not, and moving my eyes left-and-right is not what I want to do all the time.
There is another reason for <T const&> not mentioned before - code alignment. Let's see which declaration is easier to read:
void append(std::string& str,
const std::vector<std::string>& elements,
const char* delimiter);
void append(std::string& str,
std::vector<std::string> const& elements,
char const* delimiter);
<const T&> hides the type in the middle, while <T const&> exposes it on the left side. <T const&> aligns the type to the same column sliding from top to bottom can be with much less left-right eye movement when looking for a specific argument's type.
For the same reason I like the trailing return type notation (auto f(...) -> R)