How to avoid `operator[](const char*)` ambiguity? - c++

Consider the following code:
class DictionaryRef {
public:
operator bool() const;
std::string const& operator[](char const* name) const;
// other stuff
};
int main() {
DictionaryRef dict;
char text[256] = "Hello World!";
std::cout << dict[text] << std::endl;
}
This produces the following warning when compiled with G++:
warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
note: candidate 1: const string& DictionaryRef::operator[](const char*) const
note: candidate 2: operator[](long int, char*) <built-in>
I know what this means (and the cause is been explained in operator[](const char *) ambiguity) but I'm looking for a way to ensure correct behavior/resolve the warning without changing my class design - since it makes perfect sense for a class to have both a boolean conversion, and a [](const char*) operator.
What's the purpose of operator[](long int, char*) besides generating random compiler warnings? I can't imagine someone writing 1["hello"] in real code.

Although you "can't imagine someone writing 1["hello"] in a real code", this is something that's legal C++, as a consequence of the commutative [] inherited from C. Reasonable or not, that's how the language is defined, and it's unlikely to be changing for us.
The best way to avoid the ambiguity is to add explicit to the boolean conversion - it's very rare that we ever want a non-explicit operator bool().
An alternative is to replace operator bool() with an operator void*(), which will still satisfy boolean tests, but not convert to integer.

Related

Supposedly ambiguous explicit conversion operator in MSVC, not in gcc or clang

Consider the following class that implements a user-conversion to std::string and const char*:
class String {
public:
String(std::string_view s) : str{s} {}
operator std::string() const {
return str;
}
operator const char*() const {
return str.c_str();
}
private:
std::string str;
};
int main()
{
static_assert(std::is_convertible_v<String, std::string>);
String s("Hello World");
auto x = static_cast<std::string>(s);
return 0;
}
MSVC tells me that static_casting to std::string is ambiguous, while clang and gcc do not: https://godbolt.org/z/7de5YvTno
<source>(20): error C2440: 'static_cast': cannot convert from 'String' to 'std::string'
<source>(20): note: No constructor could take the source type, or constructor overload resolution was ambiguous
Compiler returned: 2
Which compiler is right?
As a follow-up question: I can fix the ambiguity by marking the conversion operations explicit. But then std::is_convertible_v<String, std::string> returns false. Is there a type trait is_explicitly_convertible or is_static_castible or similar?
PS: I know the simplest and cleanest solution would be to have a non-copying conversion operator to const std::string&, but I still want to understand why MSVC rejects the code.
This is CWG2327.
As written, the direct-initialization specified for static_cast strictly considers constructors for std::string, and the String argument requires incomparable user-defined conversions to satisfy either the move constructor or the converting constructor from const char*. Note that, regardless of the set of conversion functions available, a constructor must be called, which is the missed copy elision mentioned in the issue.
The intent is that constructors and conversion functions are simultaneously considered for the actual initialization of x. This is a bit unusual in that member functions of std::string and String are in the same overload set, but it resolves the ambiguity because calling operator std::string is an exact match for the (implied object) argument, and it allows x to be the return value from that function in the ordinary C++17 sense.
MSVC is implementing the standard as written, while GCC and Clang are implementing (something like) the intended resolution.
(std::is_constructible is more or less the direct-initialization trait you also asked about.)

Why is this C++ expression involving overloaded operators and implicit conversions ambiguous?

operator bool breaks the use of operator< in the following example. Can anyone explain why bool is just as relevant in the if (a < 0) expression as the specific operator, an whether there is a workaround?
struct Foo {
Foo() {}
Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b) {
return true;
}
};
int main() {
Foo a, b;
if (a < 0) {
a = 0;
}
return 1;
}
When I compile, I get:
g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
if (a < 0) {
^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
friend bool operator<(const Foo& a, const Foo& b)
The problem here is that C++ has two options to deal with a < 0 expression:
Convert a to bool, and compare the result to 0 with built-in operator < (one conversion)
Convert 0 to Foo, and compare the results with < that you defined (one conversion)
Both approaches are equivalent to the compiler, so it issues an error.
You can make this explicit by removing the conversion in the second case:
if (a < Foo(0)) {
...
}
The important points are:
First, there are two relevant overloads of operator <.
operator <(const Foo&, const Foo&). Using this overload requires a user-defined conversion of the literal 0 to Foo using Foo(int).
operator <(int, int). Using this overload requires converting Foo to bool with the user-defined operator bool(), followed by a promotion to int (this is, in standardese, different from a conversion, as has been pointed out by Bo Persson).
The question here is: From whence does the ambiguity arise? Certainly, the first call, which requires only a user-defined conversion, is more sensible than the second, which requires a user-defined conversion followed by a promotion?
But that is not the case. The standard assigns a rank to each candidate. However, there is no rank for "user-defined conversion followed by a promotion". This has the same rank as only using a user-defined conversion. Simply (but informally) put, the ranking sequence looks a bit like this:
exact match
(only) promotion required
(only) implicit conversion required (including "unsafe" ones inherited from C such as float to int)
user-defined conversion required
(Disclaimer: As mentioned, this is informal. It gets significantly more complex when multiple arguments are involved, and I also didn't mention references or cv-qualification. This is just intended as a rough overview.)
So this, hopefully, explains why the call is ambiguous. Now for the practical part of how to fix this. Almost never does someone who provides operator bool() want it to be implicitly used in expressions involving integer arithmetic or comparisons. In C++98, there were obscure workarounds, ranging from std::basic_ios<CharT, Traits>::operator void * to "improved" safer versions involving pointers to members or incomplete private types. Fortunately, C++11 introduced a more readable and consistent way of preventing integer promotion after implicit uses of operator bool(), which is to mark the operator as explicit. This will remove the operator <(int, int) overload entirely, rather than just "demoting" it.
As others have mentioned, you can also mark the Foo(int) constructor as explicit. This will have the converse effect of removing the operator <(const Foo&, const Foo&) overload.
A third solution would be to provide additional overloads, e.g.:
operator <(int, const Foo&)
operator <(const Foo&, int)
The latter, in this example, will then be preferred over the above-mentioned overloads as an exact match, even if you did not introduce explicit. The same goes e.g. for
operator <(const Foo&, long long)
which would be preferred over operator <(const Foo&, const Foo&) in a < 0 because its use requires only a promotion.
Because compiler can not choose between bool operator <(const Foo &,const Foo &) and operator<(bool, int) which both fits in this situation.
In order to fix the issue make second constructor explicit:
struct Foo
{
Foo() {}
explicit Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b)
{
return true;
}
};
Edit:
Ok, at last I got a real point of the question :) OP asks why his compiler offers operator<(int, int) as a candidate, though "multi-step conversions are not allowed".
Answer:
Yes, in order to call operator<(int, int) object a needs to be converted Foo -> bool -> int. But, C++ Standard does not actually say that "multi-step conversions are illegal".
§ 12.3.4 [class.conv]
At most one user-defined conversion (constructor or conversion
function) is implicitly applied to a single value.
bool to int is not user-defined conversion, hence it is legal and compiler has the full right to chose operator<(int, int) as a candidate.
This is exactly what the compiler tells you.
One approach for solving the if (a < 0) for the compiler is to use the Foo(int x) constructor you've provided to create object from 0.
The second one is to use the operator bool conversion and compare it against the int (promotion). You can read more about it in Numeric promotions section.
Hence, it is ambiguous for the compiler and it cannot decide which way you want it to go.

How is the implicit type conversion priority determined?

Here is the code:
class A{
public:
int val;
char cval;
A():val(10),cval('a'){ }
operator char() const{ return cval; }
operator int() const{ return val; }
};
int main()
{
A a;
cout << a;
}
I am running the code in VS 2013, the output value is 10, if I comment out operator int() const{ return val; }, the output value will then become a.
My question is how does the compiler determine which implicit type conversion to choose, I mean since both int and char are possible options for the << operator?
Yes, this is ambiguous, but the cause of the ambiguity is actually rather surprising. It is not that the compiler cannot distinguish between ostream::operator<<(int) and operator<<(ostream &, char); the latter is actually a template while the former is not, so if the matches are equally good the first one will be selected, and there is no ambiguity between those two. Rather, the ambiguity comes from ostream's other member operator<< overloads.
A minimized repro is
struct A{
operator char() const{ return 'a'; }
operator int() const{ return 10; }
};
struct B {
void operator<< (int) { }
void operator<< (long) { }
};
int main()
{
A a;
B b;
b << a;
}
The problem is that the conversion of a to long can be via either a.operator char() or a.operator int(), both followed by a standard conversion sequence consisting of an integral conversion. The standard says that (§13.3.3.1 [over.best.ics]/p10, footnote omitted):
If several different sequences of conversions exist that each convert
the argument to the parameter type, the implicit conversion sequence
associated with the parameter is defined to be the unique conversion
sequence designated the ambiguous conversion sequence. For the
purpose of ranking implicit conversion sequences as described in
13.3.3.2, the ambiguous conversion sequence is treated as a user-defined sequence that is indistinguishable from any other
user-defined conversion sequence. *
Since the conversion of a to int also involves a user-defined conversion sequence, it is indistinguishable from the ambiguous conversion sequence from a to long, and in this context no other rule in §13.3.3 [over.match.best] applies to distinguish the two overloads either. Hence, the call is ambiguous, and the program is ill-formed.
* The next sentence in the standard says "If a function that uses the ambiguous conversion sequence is selected as the best viable function, the call will be ill-formed because the conversion of one of the arguments in the call is ambiguous.", which doesn't seem necessarily correct, but detailed discussion of this issue is probably better in a separate question.
It shouldn't compile, since the conversion is ambiguous; and it doesn't with my compiler: live demo. I've no idea why your compiler accepts it, or how it chooses which conversion to use, but it's wrong.
You can resolve the ambiguity with an explicit cast:
cout << static_cast<char>(a); // uses operator char()
cout << static_cast<int>(a); // uses operator int()
Personally, I'd probably use named conversion functions, rather than operators, if I wanted it to be convertible to more than one type.
A debugging session gave the result. One is globally defined operator<< and other one is class method. You guess which one is calling which.
Test.exe!std::operator<<<std::char_traits<char> >(std::basic_ostream<char,std::char_traits<char> > & _Ostr, char _Ch)
msvcp120d.dll!std::basic_ostream<char,std::char_traits<char> >::operator<<(int _Val) Line 292 C++
I am not a language lawyer, but I believe compiler is giving precedence to member-function first.

C++: force the compiler to use one of two competing operators

I've been playing with C++ recently, and I just stumbled upon an interesting precedence issue. I have one class with two operators: "cast to double" and "+". Like so:
class Weight {
double value_;
public:
explicit Weight(double value) : value_(value) {}
operator double() const { return value_; }
Weight operator+(const Weight& other) { return Weight(value_ + other.value_); }
};
When I try to add two instances of this class...
class Weighted {
Weight weight_;
public:
Weighted(const Weight& weight) : weight_(weight) {}
virtual Weighted twice() const {
Weight w = weight_ + weight_;
return Weighted(w);
}
};
...something unexpected happens: the compiler sees the "+" sign and casts the two weight_s to double. It then spits out a compilation error, because it can't implicitly cast the resulting double back to a Weight object, due to my explicit one-argument constructor.
The question: how can I tell the compiler to use my own Weight::operator+ to add the two objects, and to ignore the cast operator for this expression? Preferably without calling weight_.operator+(weight_), which defeats the purpose.
Update: Many thanks to chris for pointing out that the compiler is right not to use my class's operator+ because that operator is not const and the objects that are being +ed are const.
I now know of three ways to fix the above in VS2012. Do see the accepted answer from chris for additional information.
Add the explicit qualifier to Weight::operator double(). This
doesn't work in VS 2012 (no support), but it stands to reason that
it's a good solution for compilers that do accept this approach (from the accepted answer).
Remove the virtual qualifier from method Weighted::twice, but don't ask me why this works in VS.
Add the const qualifier to method Weight::operator+ (from the accepted answer).
Current version:
First of all, the virtual should have nothing to do with it. I'll bet that's a problem with MSVC, especially considering there's no difference on Clang. Anyway, your twice function is marked const. This means that members will be const Weight instead of Weight. This is a problem for operator+ because it only accepts a non-const this. Therefore, the only way the compiler can go is to convert them to double and add those.
Another problem is that adding explicit causes it to compile. In fact, this should remove the compiler's last resort of converting to double. This is indeed what happens on Clang:
error: invalid operands to binary expression ('const Weight' and 'const Weight')
Weight w = weight_ + weight_;
note: candidate function not viable: 'this' argument has type 'const Weight', but method is not marked const
Finally, making operator+ const (or a free function) is the correct solution. When you do this, you might think you'll add back this route and thus have another error from ambiguity between this and the double route, but Weight to const Weight & is a standard conversion, whereas Weight to double is a user-defined conversion, so the standard one is used and everything works.
As of the updated code in the question, this is fine. The reason it won't compile is the fault of MSVC. For reference, it does compile on Clang. It also compiles on MSVC12 and the 2013 CTP.
You may be storing the result in a Foo, but there is still an implicit conversion from double to Foo needed. You should return Foo(value_ + other.value_) in your addition operator so that the conversion is explicit. I recommend making the operator a free function as well because free operators are (almost) always at least as good as members. While I'm at it, a constructor initializer list would be a welcome change, too.
In addition, from C++11 onward, a generally preferred choice is to make your conversion operator explicit:
explicit operator double() const {return value_;}
Also note the const added in because no state is being changed.

c++ conversion operator overloading, enums, ints and chars

When I try to compile (with gcc 4.3.4) this code snippet:
enum SimpleEnum {
ONEVALUE
};
void myFunc(int a) {
}
void myFunc(char ch) {
}
struct MyClass {
operator int() const { return 0; };
operator SimpleEnum() const { return ONEVALUE; };
};
int main(int argc, char* argv[]) {
myFunc(MyClass());
}
I get this error:
test.cc: In function "int main(int, char**)":
test.cc:17: error: call of overloaded "myFunc(MyClass)" is ambiguous
test.cc:5: note: candidates are: void myFunc(int)
test.cc:8: note: void myFunc(char)
I think I (almost) understand what the problem is, i.e. (simplifying it a lot) even if I speak about "char" and "enum", they all are integers and then the overloading is ambiguous.
Anyway, the thing I don't really understand is that if I remove the second overloading of myFunc OR one of the conversion operators of MyClass, I have no compilation errors.
Since I'm going to change A LOT of old code because of this problem (I'm porting code from an old version of HP-UX aCC to g++ 4.3.4 under Linux), I would like to understand better the whole thing in order to choose the best way to modify the code.
Thank you in advance for any help.
The conversion from MyClass is ambiguous, as there is one conversion to int and one to the enum, which is itself implicitly convertible to int, and both are equally good conversions. You can just make the call explicit, though, by specifying which conversion you want:
myfunc(int(MyClass()));
Alternatively, you might like to rethink why you have a function that has separate overloads for int and char, perhaps that could be redesigned as well.
enums are types in C++, unlike C.
There are implicit conversions for both enum -> char and enum -> int. The compiler just doesn't know which one to choose.
EDIT: After trying with different tests:
When the definition for custom conversion MyClass -> int is removed, code compiles.
Here there is implicit conversion for enum to int and so it is the one favored by the compiler over the one to char. Test here.
When the definition for void myFunc(int) is removed compilation fails.
Compiler tries to convert from MyClass to char and finds that, not having a user defined conversion operator char(), both user defined int() and SimpleEnum() may be used. Test here.
When you add a char() conversion operator for MyClass compilation fails with the same error as if not.
Test here.
So the conclusion I come up with here is that in your originally posted code compiler has to decide which of the two overloaded versions of myFunc should be called.
Since both conversions are possible:
MyClass to int via user defined conversion operator.
MyClass to int via user defined conversion (MyClass to SimpleEnum) + implicit conversion (SimpleEnum to char)
compiler knows not which one to use.
I would have expected that the int overload is called. Tried a few compilers and got different results. If you are going to remove anything, remove the user conversion operator to int, since enums have a standard conversion to ints.