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.
Related
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.
While I was on a short break, my workplace switched to using a static code analyzer.
They ran it over the project I am working on and one particular problem flagged by the analyzer goes like this (simplified example):
struct calcSomething
{
int result;
calcSomething() : result(0) {}
void operator()(const int v) { /*does something*/ }
operator int() const { return result; }
};
void foo()
{
std::vector<int> myvector(10);
// exercise for reader: stick some values in `myvector`
int result = std::for_each(myvector.begin(), myvector.end(), calcSomething());
}
The analyzer flags the following issues:
warning: CodeChecker: 'operator int' must be marked explicit to avoid unintentional implicit conversions [google-explicit-constructor]
operator int() const { return result; }
The suggested fix to the functor reads:
struct calcSomething
{
...
explicit operator int() const { return result; }
};
But if I fix my functor as suggested, the static analyzer quickly flags the following issue:
warning: CodeChecker: no viable conversion from '(anonymous namespace)::calcSomething' to 'int' [clang-diagnostic-error]
I now need to add the explicit cast:
void foo()
{
...
int total = static_cast<int>(std::for_each(myvector.begin(), myvector.end(), calcSomething()));
}
The above example is a mere simplification of the real problem which would otherwise just add filler and no substance.
I have seen plenty of examples of functors like the one I describe here in text books and programming reference web pages.
I have never considered these unsafe. I have never seen anyone flag these as unsafe.
So does the code analyzer have a point?
Or is it a little overzealous to make my functor's conversion operator explicit and as a result make me add the static cast?
Purely from an aesthetic point, I feel that a simple problem with an elegant solution now accrues a lot of ugly syntactic padding.
But perhaps that is the price we pay for writing safe(r) code.
side note: TIL that explicit applies not only to ctors
Edit
It seems some people are unable to read beyond the example code I provided (pretty textbook stuff) and still suggest other algorithms/idioms, completely failing to see that the actual question is about conversion operators on functors whose sole purpose is to calculate and return an algorithm's result.
If the question was about how to improve on an adding algorithm, then the title would have said so.
So I decided to hide any implementation details in this edit to make it easier for these people.
Sorry that some of the comments below now no longer make any sense, but the record got stuck so I had to move the needle a bit in order to move things forward (hopefully).
I would do away with any conversion operators altogether. What's wrong with:
int result = std::for_each(...).get();
where get() does the same as your current operator int.
You know that the result of for_each is not an integer, it's your function object. Why, why would you want to avoid making the conversion from a function to a value explicit? It is, by all means, a questionable idea. Sure, you can still do it, but you want clean warning-free code right? Well, clean, warning free code, in my book, should not auto-convert functions to integers. I agree static_cast is almost equally ugly, that's why I am suggesting a named function
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.
Consider the following class:
class Test
{
public:
Test( char );
Test( int );
operator const char*();
int operator[]( unsigned int );
};
When I use it:
Test t;
Test& t2 = t[0];
I get a compiler error where it can't figure out which operator[] to use. MSVC and Gcc both. Different errors. But same problem.
I know what is happening. It can construct a Test from either an int or a char, and it can either use Test::operator[] or it can cast to const char* and use the built char*[].
I'd like it to prefer Test::operator[].
Is there a way to specify a preference like this?
UPDATE: First, I'll agree with response below that there is no language feature that lets you specify a priority for conversions. But like juanchopanza found below - you can create such a preference indirectly by making one conversion require no casts, and make the other require a cast. E.g. unsigned int as the argument for operator[] won't work but, using int will.
The default index argument is unsigned - for gcc and msvc anyway - so making the index argument unsigned will cause the compiler to prefer the right operator.
#Konrad: about implicit conversions to built-in types. Agree generally. But I need it in this case.
No there isn't. And this kind of ambiguity is why automatic conversions to another type (like operator char*) are not recommended.
You can always make the constructors explicit – or, as john has noted, omit the conversion operator. I’d usually recommend doing all of these things but since your code is a toy example it’s hard to say what is appropriate in your situation.
That said, the following should work:
Test const& t2 = t.operator[](0);
(Note the added const – otherwise you’d bind a temporary to non-const reference which doesn’t work either.)
I've implemented container classes like yours before -- it is entirely possible to avoid ambiguity problems while maintaining a clean, intuitive syntax.
First, you need to take const-ness into account. Usually, a const char* cast will not modify the object, so make this a const member function:
operator const char*() const;
Also, your [] operator is returning an int instead of int&, so this should probably be const too:
int operator[]( unsigned int ) const;
If you want to be able to assign a value to an indexed element (mytest[5]=2), you would add another operator function like this (this doesn't replace the const version above -- keep both):
int& operator[](unsigned int);
Next, you will want to accept indexes that are not just unsigned int. A literal integer has the int type. To avoid problems related to index type, you need to overload operator for alternatives. These overloads will just static_cast the index to your native index type and pass it to the native overload (the one for the native index type). One of these overloads might look like:
int operator[]( int idx ) const {
return operator[]( static_cast<unsigned int>(idx) );
}
Instead of adding a bunch of overloads, you could use a single template member function to cover all possibilities. The template function will get used for any index types that do not already have a non-template overload. And this preference for non-template overloads is unambiguous. Essentially, this implements your request for a preferred operator, but const-ness is not templatable, so two versions would be required if you have a const and a non-const [] operator. This catch-all member function might look like this (keep in mind that its definition must stay in your class' declaration and cannot be moved to an implementation file):
template <typename IT>
int operator[]( const IT& idx ) const {
return operator[]( static_cast<unsigned int>(idx) );
}
If you added a non-const version of the [] operator, then you would also need:
template <typename IT>
int& operator[]( const IT& idx ) {
return operator[]( static_cast<unsigned int>(idx) );
}
Enjoy!
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.