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!
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 have a function that accepts a const reference to a valarray, and I want to be able to slice the array and pass the slice into another function that expects a const slice_array. I know that I can just use operator[] and a slice to get a new, copied valarray out of the original valarray, but I want to avoid the copy. Everything is const, so I feel like that should be okay. However, the documentation for the subscript operator for valarray ONLY returns a slice_array objects when applied to non-const valarrays. This feels kind of a major deficiency in the valarray class. Am I missing something? How do I get a slice of a const valarray without incurring a copy?
Here is an example of what I'm talking about:
void foo( const valarray<double> &big_stuff )
{
const slice_array<double> small_stuff = big_stuff[slice(start, size, stride)];
bar( small_stuff );
}
Thanks!
How do I get a slice of a const valarray without incurring a copy?
There is no way to do this, because of std::valarray not contain std::slice_array
for any request, so it cat not give you just link(pointer,reference) to std::slice_array that you request.
But std::slice_array can contains only three machine words,
hard to imagine situation where not copy of std::slice_array can optimize something. For example from gcc/libstdc++ (only data, functions stripped):
template<typename _Tp>
class slice_array {
const size_t _M_sz;
const size_t _M_stride;
const _Array<_Tp> _M_array;
};
where _Array:
template<typename _Tp>
struct _Array { _Tp* const __restrict__ _M_data; };
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.
suppose I design a String class myself and I want to overload the operator[] functions, here are the choices:
String operator[](const size_t index);
String& operator[](const size_t index);
const String& operator[](const size_t index) const;
any other combination of const, non-const and reference return type.
The [] operator of a string returns a character, not a string. Depending on circumstances, you should implement one or two of them:
const char& operator[] ( size_t index ) const;
char& operator[] ( size_t index );
The first one produces a reference that cannot be modified. If your string is immutable, that is all you need.
The second one produces a reference that can be modified. You can use it to implement clever stuff, such as copy-on-modify and reference counting.
Some people prefer to have a signed parameter type for operator[], both to be more similar to built-in operator[] (they too support negative indices) and also to be able to detect negative value arguments (in case you have an out of bounds check).
The type that the C++ compiler uses to evaluate calling the built-in operator[] is ptrdiff_t, so you will sometimes find the following
char &operator[](ptrdiff_t index);
char operator[](ptrdiff_t index) const;
I usually just use a plain int parameter type.
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.