I'm trying to implement the vector like and the map like []
operator for a class. But I get error messages from my compilers (g++ and clang++). Found out that they only occurs if the class has also conversion operators to integer types.
Now I have two problems. The first is that I don't know why the compiler can't distinguish between [](const std::string&)
and [](size_t)
when the class has conversion operators to ints.
The second... I need the conversion and the index operator. How to fix that?
works:
#include <stdint.h>
#include <string>
struct Foo
{
Foo& operator[](const std::string &foo) {}
Foo& operator[](size_t index) {}
};
int main()
{
Foo f;
f["foo"];
f[2];
}
does not work:
#include <stdint.h>
#include <string>
struct Foo
{
operator uint32_t() {}
Foo& operator[](const std::string &foo) {}
Foo& operator[](size_t index) {}
};
int main()
{
Foo f;
f["foo"];
f[2];
}
compiler error:
main.cpp: In function 'int main()':
main.cpp:14:9: error: ambiguous overload for 'operator[]' in 'f["foo"]'
main.cpp:14:9: note: candidates are:
main.cpp:14:9: note: operator[](long int, const char*) <built-in>
main.cpp:7:7: note: Foo& Foo::operator[](const string&)
main.cpp:8:7: note: Foo& Foo::operator[](size_t) <near match>
main.cpp:8:7: note: no known conversion for argument 1 from 'const char [4]' to 'size_t {aka long unsigned int}'
The problem is that your class has a conversion operator to uint32_t
, so the compiler does not know whether to:
std::string
from the string literal and invoke your overload accepting an std::string
;Foo
object into an uint32_t
and use it as an index into the string literal.While option 2 may sound confusing, consider that the following expression is legal in C++:
1["foo"];
This is because of how the built-in subscript operator is defined. Per Paragraph 8.3.4/6 of the C++11 Standard:
Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way that
E1[E2]
is identical to*((E1)+(E2))
. Because of the conversion rules that apply to +, ifE1
is an array andE2
an integer, thenE1[E2]
refers to theE2
-th member ofE1
. Therefore, despite its asymmetric appearance, subscripting is a commutative operation.
Therefore, the above expression 1["foo"]
is equivalent to "foo"[1]
, which evaluates to o
. To resolve the ambiguity, you can either make the conversion operator explicit
(in C++11):
struct Foo
{
explicit operator uint32_t() { /* ... */ }
// ^^^^^^^^
};
Or you can leave that conversion operator as it is, and construct the std::string
object explicitly:
f[std::string("foo")];
// ^^^^^^^^^^^^ ^
Alternatively, you can add a further overload of the subscript operator that accepts a const char*
, which would be a better match than any of the above (since it requires no user-defined conversion):
struct Foo
{
operator uint32_t() { /* ... */ }
Foo& operator[](const std::string &foo) { /* ... */ }
Foo& operator[](size_t index) { /* ... */ }
Foo& operator[](const char* foo) { /* ... */ }
// ^^^^^^^^^^^
};
Also notice, that your functions have a non-void return type, but currently miss a return
statement. This injects Undefined Behavior in your program.
The problem is that f["foo"]
can be resolved as:
"foo"
to std::string
(be it s
) and do f[s]
calling Foo::operator[](const std::string&)
.f
to integer calling Foo::operator int()
(be it i
) and do i["foo"]
using the well known fact that built-in []
operator is commutative.Both have one custom type conversion, hence the ambiguity.
The easy solution is to add yet another overload:
Foo& operator[](const char *foo) {}
Now, calling f["foo"]
will call the new overload without needing any custom type conversion, so the ambiguity is broken.
NOTE: The conversion from type char[4]
(type type of "foo"
) into char*
is considered trivial and doesn't count.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With