In my code I created two template functions overlading the [] operator (an excercise to build my version of STL map):
const V& operator[](const K& key) const; // X = map["kuku"]
V& operator[](const K& key); // map["kuku"] = x
I then use the operator through the following function:
invokeStkCmd (my_stacks[stk_name], my_cmds[crnt_word]);
whose template is
invokeStkCmd (StackComp* stkPtr, const shake_cmds_t cmd)
This invocation uses the V& operator[](const K& key) template (at least for the second parameter) which can add an entry to the map and not the other template as I intended. Referring to a recent operator overloading post here at Stack Overflow, it seems my opertaor overloading template functions are OK. Do I need to do something in my invokeStkCmd function or are my operator overloading functions not accurate after all?
This is a common issue when dealing with objects whose behaviour differs largely when they are to be modified. It is caused by the simple fact that the overload is resolved based on solely argument types, not by the return type or use of the return value. So the easiest solution is using a const_cast to make the map object const just before the indexing takes place:
invokeStkCmd (my_stacks[stk_name], const_cast<my_map<Whatever> const&>(my_cmds)[crnt_word]);
This might not satisfy you because of its verbosity and error-proneness; in this case there is also an advanced way exploiting the fact that cast operator overloads are resolved based on their return type, differing from ordinary functions. I have posted an answer like this here. Note that it is pretty hard to do it right.
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'm having some trouble defining hash and equality functions for objects. I'm using the objects as keys for std::unordered_map's. I have two keys aKey and unaccessibleKey. I can add an equailty operator overload to aKey, but I can't to unaccessibleKey because it's "unaccessible". I have tried doing the following, but I'm not sure if I'm using the right syntax for everything, and I don't know how to define an equality function for unaccessibleKey. Here's what I've tried:
struct aKeyHash
{
std::size_t operator()(const aKey& k) const
{
return k.getnonconstmem()->nonconstfunc();
};
}
struct unaccessibleKeyHash
{
std::size_t operator()(const unaccessibleKey& k) const
{
return k.noncostmem;
};
}
bool UnaccessibleEqualityFunction(const unaccessibleKey& p1, const unaccessibleKey& p2)
{???} //i dont know how to define this
std::unordered_map<aKey, std::unordered_map<unaccessibleKey, aValue, unaccessibleKeyHash, unaccessibleEqualityFunctions>, aKeyHash>
Am I doing this right (aside from the function that I don't know how to define)? As a side note, when I tried calling k.getnonconstmem()->nonconstfunction() I get an error.
It would be possible to use unaccessibleKey::nonconstmem as the key itself because it's actually a hashed int, but that may lead to complications later down the line that I don't want to deal with.
So my questions the are: 1. do I have the right syntax for the hashes, 2. how do I define the equality function, 3. why would I get the error with the const/nonconst mixing?
do I have the right syntax for the hashes
The hash structures themselves have correct syntax. Whether the definitions of the hash functions are correct, depends on the definition of the target types. In particular, if getnonconstmem() is a non-const function, then calling it on a const reference is ill-formed. You may only call const functions on const objects.
how do I define the equality function
Return true when the objects have equal logical state, and false otherwise. An example, which assumes that the state consists of a single member:
return p1.member == p2.member;
why would I get the error with the const/nonconst mixing?
The shown code is not sufficient to explain why there would be errors. You will get errors if you try to modify, or call non-const functions on const objects, or any object through pointer or reference to const.
Basically, I'm trying to make template map/dictionary class for c++ (I know this have already been done, assume I'm masochistic).
I started up, writing this skeleton:
#pragma once
template <class T>
class AssArray
{
int _size;
int _position;
public:
AssArray(int size);
~AssArray(void);
const T& operator [](char* b) const;
T& operator [](char* b) const;
//I read this should be done sth like this when researching, though an explanation would be nice.
};
Now, I need to be able to get (T=AssArray["llama"]), set (AssArray["llama"]= T) and override (AssArray["llama"]= newT).
Doing this is pretty straight forward, just loop it through etc, the real problem here is the operators;
if I use AssArray["llama"]= T, how am I supposed to get the value of T into the operator overloading-function?
I've only found explanations describing the solutions briefly, and can not really follow.
Please enlighten me.
All you have to do is correct your signatures like so:
const T& operator [](char* b) const;
T& operator [](char* b);
I've removed the const qualifier from the second operator.
if I use AssArray["llama"]=T, how am I supposed to get the value of T into the operator overloading-function?
You don't. You just return a reference to where the new value should be stored, and the compiler will take care of the rest. If "llama" does not exist in the array, you need to create an entry for it, and return a reference to that entry.
Since the operator[] returns a reference to T if you want to say assArray["str"] = T the type T has to know what to do with the operator=. If T does not have the operator= overloaded you have to overload the operator= in type T.
operator[] has nothing to do with assignments. It should just return the element at the given index.
I have a couple of pure virtual classes, Matrix and Vector. Throughout my code base I try to only create dependencies on them and not their concrete subclasses e.g. SimpleTransformationMatrix44 and SimpleTranslationVector4. The motivation for this is that I can use third party (adapted) classes in place of mine without much trouble.
I would like to overload the arithmetic operators (sourced from here):
T T::operator +(const T& b) const;
T T::operator -(const T& b) const;
T T::operator *(const T& b) const;
I want to declare them in the pure virtual classes so that it is valid to perform the operations on references/pointers to them, the problem being that an abstract class cannot be returned by value. The best solution I can think of is something like this:
std::unique_ptr<T> T::operator +(const T& b) const;
std::unique_ptr<T> T::operator -(const T& b) const;
std::unique_ptr<T> T::operator *(const T& b) const;
Which allows this (without down casts!):
std::unique_ptr<Matrix> exampleFunction(const Matrix& matrix1, const Matrix& matrix2)
{
std::unique_ptr<Matrix> product = matrix1 * matrix2;
return std::move(product);
}
A pointer seems to be the only option in this case since returning a value is invalid and returning a reference is just plain silly.
So I guess my question is: Have I gone off the plot with this idea? If you saw it in some code you were working on would you be thinking WTF to yourself? Is there a better way to achieve this?
First off: Overloading operators is something that applies best to value types. As you have found out, polymorphism doesn't play well with it. If you are willing to walk on crutches, this might help, though:
If you follow the advice of Stackoverflow's operator overloading FAQ, you will implement operator+() as a non-member atop of operator+=(). The latter returns a reference. It's still a problem, because it can only return a base class reference, but as long as you only use it for expressions expecting that, you are fine.
If you then templatize operator+() as DeadMG suggested, you might be able to do what you want:
template<typename T>
T operator+(const T lhs, const T& rhs)
{
lhs += rhs;
return lhs;
}
Note that this would catch any T for which no better-matching operator+() overload can be found. (This might seem like a good idea — until you forget to include a header and this operator catches the x+y, making the code compile, but silently produce wrong results.) So you might want to restrict this.
One way would be to put it into the same namespace as you matrix and vector types. Or you use a static_assert to ensure only types derived from the two are passed in.
Have I gone off the plot with this idea?
Yes. The appropriate solution to this problem is to implement flexibility via template, not inheritance. Inheritance is most definitely not suited to this kind of problem. In addition, it's usual (if not mandated) to have the dimensions of the vector or matrix specified as a template parameter in addition, instead of at run-time.
I had assumed that the canonical form for operator+, assuming the existence of an overloaded operator+= member function, was like this:
const T operator+(const T& lhs, const T& rhs)
{
return T(lhs) +=rhs;
}
But it was pointed out to me that this would also work:
const T operator+ (T lhs, const T& rhs)
{
return lhs+=rhs;
}
In essence, this form transfers creation of the temporary from the body of the implementation to the function call.
It seems a little awkward to have different types for the two parameters, but is there anything wrong with the second form? Is there a reason to prefer one over the other?
I'm not sure if there is much difference in the generated code for either.
Between these two, I would (personally) prefer the first form since it better conveys the intention. This is with respect to both your reuse of the += operator and the idiom of passing templatized types by const&.
With the edited question, the first form would be preferred. The compiler will more likely optimize the return value (you could verify this by placing a breakpoint in the constructor for T). The first form also takes both parameters as const, which would be more desirable.
Research on the topic of return value optimization, such as this link as a quick example: http://www.cs.cmu.edu/~gilpin/c++/performance.html
I would prefer the first form for readability.
I had to think twice before I saw that the first parameter was being copied in. I was not expecting that. Therefore as both versions are probably just as efficient I would pick them one that is easier to read.
const T operator+(const T& lhs, const T& rhs)
{
return T(lhs)+=rhs;
}
why not this if you want the terseness?
My first thought is that the second version might be infinitessimally faster than the first, because no reference is pushed on the stack as an argument. However, this would be very compiler-dependant, and depends for instance on whether the compiler performs Named Return Value Optimization or not.
Anyway, in case of any doubt, never choose for a very small performance gain that might not even exist and you more than likely won't need -- choose the clearest version, which is the first.
Actually, the second is preferred. As stated in the c++ standard,
3.7.2/2: Automatic storage duration
If a named automatic object has
initialization or a destructor with
side effects, it shall not be
destroyed before the end of its block,
nor shall it be eliminated as an
optimization even if it appears to be
unused, except that a class object or
its copy may be eliminated as
specified in 12.8.
That is, because an unnamed temporary object is created using a copy constructor, the compiler may not use the return value optimization. For the second case, however, the unnamed return value optimization is allowed. Note that if your compiler implements named return value optimization, the best code is
const T operator+(const T& lhs, const T& rhs)
{
T temp(lhs);
temp +=rhs;
return temp;
}
I think that if you inlined them both (I would since they're just forwarding functions, and presumably the operator+=() function is out-of-line), you'd get near indistinguishable code generation. That said, the first is more canonical. The second version is needlessly "cute".