This question already has answers here:
C++ overload resolution, conversion operators and const
(3 answers)
Closed 5 years ago.
I'm writing a interface to a 3rd party library. It manipulates objects through a C interface which essentially is a void*. Here is the code simplified:
struct LibIntf
{
LibIntf() : opaquePtr{nullptr} {}
operator void *() /* const */ { return opaquePtr; }
operator void **() { return &opaquePtr; }
void *opaquePtr;
};
int UseMe(void *ptr)
{
if (ptr == (void *)0x100)
return 1;
return 0;
}
void CreateMe(void **ptr)
{
*ptr = (void *)0x100;
}
int main()
{
LibIntf lib;
CreateMe(lib);
return UseMe(lib);
}
Everything works great until I add the const on the operator void *() line. The code then defaults silently to using the operator void **() breaking the code.
My question is why?
I'm returning a pointer through a function that doesn't modify the object. Should be able to mark it const. If that changes it to a const pointer, the compiler should error because operator void **() shouldn't be a good match for function CallMe() that just want a void *.
This is what the standard says should happen, but this is far from obvious. For quick readers, jump to the "How to fix it?" at the end.
Understanding why the const matters
Once you add the const qualifier, when you call UseMe with an instance of LibIntf, the compiler have the two following possibilities:
LibIntf →1 LibIntf →2 void** →3 void* (through operator void**())
LibIntf →3 const LibIntf →2 void* →1 void* (through operator void* const())
1) No conversion needed.
2) User-defined conversion operator.
3) Legal conversions.
Those two conversion paths are legal, so which one to choose?
The standard defining C++ answers:
[over.match.best]/1
Define ICSi(F) as follows:
[...]
let ICSi(F) denote the implicit conversion sequence that converts the ith argument in the list to the type of the ith parameter of viable function F. [over.best.ics] defines the implicit conversion sequences and [over.ics.rank] defines what it means for one implicit conversion sequence to be a better conversion sequence or worse conversion sequence than another.
Given these definitions, a viable function F1 is defined to be a
better function than another viable function F2 if for all arguments
i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then
for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type.
(I had to read it a couple times before getting it.)
This all means in your specific case than option #1 is better than option #2 because for user-defined conversion operators, the conversion of the return type (void** to void* in option #1) is considered after the conversion of the parameter type (LibIntf to const LibIntf in option #2).
In chain, this means in option #1 there is nothing to convert (latter in the conversion chain, there will but this is not yet considered) but in option #2 a conversion from non-const to const is needed. Option #1 is, thus, dubbed better.
How to fix it?
Simply remove the need to consider the non-const to const conversion by casting the variable to const (explicitly (casts are always explicit (or are called conversions))):
struct LibIntf
{
LibIntf() : opaquePtr{nullptr} {}
operator void *() const { return opaquePtr; }
operator void **() { return &opaquePtr; }
void *opaquePtr;
};
int UseMe(void *ptr)
{
if (ptr == (void *)0x100)
return 1;
return 0;
}
void CreateMe(void **ptr)
{
*ptr = (void *)0x100;
}
int main()
{
LibIntf lib;
CreateMe(lib);
// unfortunately, you cannot const_cast an instance, only refs & ptrs
return UseMe(static_cast<const LibIntf>(lib));
}
Related
template<typename Integral>
struct IntegralWrapper {
Integral _value;
IntegralWrapper() = default;
IntegralWrapper(Integral value)
: _value(value) {}
operator Integral() const {
return _value;
}
operator bool() const = delete;
};
int main() {
IntegralWrapper<int> i1, i2;
i1 * i2;
}
It's compiled successfully by gcc, but failed by MSVC and clang, with error overloaded operator '*' is ambiguous. The problem comes from the explicit deleted operator bool.
https://godbolt.org/z/nh6M11d98
Which side (gcc or clang/MSVC) is right? And why?
First of all: Deleting a function does not prevent it from being considered in overload resolution (with some minor exceptions not relevant here). The only effect of = delete is that the program will be ill-formed if the conversion function is chosen by overload resolution.
For the overload resolution:
There are candidate built-in overloads for the * operator for all pairs of promoted arithmetic types.
So, instead of using * we could also consider
auto mul(int a, int b) { return a*b; } // (1)
auto mul(long a, long b) { return a*b; } // (2)
// further overloads, also with non-matching parameter types
mul(i1, i2);
Notably there are no overloads including bool, since bool is promoted to int.
For (1) the chosen conversion function for both arguments is operator int() const instantiated from operator Integral() const since conversion from int to int is better than bool to int. (Or at least that seems to be the intent, see e.g. https://github.com/cplusplus/draft/issues/2288 and In overload resolution, does selection of a function that uses the ambiguous conversion sequence necessarily result in the call being ill-formed?).
For (2) however, neither conversion from int or bool to long is better than the other. As a result the implicit conversion sequences will for the purpose of overload resolution be the ambiguous conversion sequence. This conversion sequence is considered distinct from all other user-defined conversion sequences.
When then comparing which of the overloads is the better one, neither can be considered better than the other, because both use user-defined conversion sequences for both parameters, but the used conversion sequences are not comparable.
As a result overload resolution should fail. If I completed the list of built-in operator overloads I started above, nothing would change. The same logic applies to all of them.
So MSVC and Clang are correct to reject and GCC is wrong to accept. Interestingly with the explicit example of functions I gave above GCC does reject as expected.
To disallow implicit conversions to bool you could use a constrained conversion function template, which will not allow for another standard conversion sequence after the user-defined conversion:
template<std::same_as<int> T>
operator T() const { return _value; }
This will allow only conversions to int. If you can't use C++20, you will need to replace the concept with SFINAE via std::enable_if.
Consider the following class:
class foo
{
public:
constexpr operator bool() const noexcept;
constexpr operator void * &() noexcept;
constexpr operator void * const &() const noexcept;
constexpr operator void const * &() noexcept;
constexpr operator void const * const &() const noexcept;
};
A foo object would be used like this:
void bar(bool);
// ...
foo f;
bar(f); // error C2664: cannot convert argument 1 from 'foo' to 'bool'
// message : Ambiguous user-defined-conversion
// message : see declaration of 'bar'
The issue is that the operator bool is not considered because of its constness. If I make another function without the const qualifier, the problem is solved. If I make f const, the problem is solved as well. If I explicitly cast f to bool, the problem is solved.
Sample to work with
What are my other options and what is causing this ambiguity?
First you should have a look at this question regarding conversion precedence in C++.
Then as pointed by some comments, operator bool() was not selected by the compiler for this conversion and the ambiguous resolution message is not about it.
The ambiguity comes from constexpr operator void * &() noexcept versus constexpr operator void const * &() noexcept. The idea being: let's try to convert a non const object to something and then see if this something can be converted to bool.
Also operator void const*() and operator void*() are redundant because there is no situation when you can call one and not the other and you can get a const void* from a void*.
Also returning void const * const & does not have any advantage over returning void const* as you won't be able to modify the reference. Returning a pointer instead of a reference to a pointer at worst does not change anything, at best, prevent you from doing a double indirection.
My advice would be to remove the non const operators and replace them by an explicit setter when you want to change the underlying pointer stored in foo. But that might not be the solution you are looking for depending on your actual problem and your design choices.
Take a look at the following, simplified, code:
struct foo {
constexpr operator bool() const noexcept; // (1)
constexpr operator void * &() noexcept; // (2)
constexpr operator void const * &() noexcept; // (3)
};
void bar(bool);
// ...
foo f;
bar(f);
The implicit conversion rules state the following:
Order of the conversions
Implicit conversion sequence consists of the following, in this order:
zero or one standard conversion sequence;
zero or one user-defined conversion;
zero or one standard conversion sequence.
The conversion of the argument in bar is one user-defined conversion, followed by one standard conversion sequence.
Continued:
... When converting from one built-in type to another built-in type, only one standard conversion sequence is allowed.
A standard conversion sequence consists of the following, in this order:
zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion;
zero or one numeric promotion or numeric conversion;
zero or one function pointer conversion; (since C++17)
zero or one qualification adjustment.
There are planty of types that can be used where a bool is required, and pointers are amongst them, so for overloads #2 and #3 all that is required is one "lvalue-to-rvalue conversion". To use overload #1 the compiler will have to perform a "qualification adjustment" (const bool to bool).
How to fix this:
Remove ambiguity by adding the constexpr operator bool() noexcept;, which fits bar exactly.
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.
I am curious about the following code, somebody could explain why does it call bool func first ? isn`t "str" more suitable for arg type string ?
void a(bool input)
{
cout<<"I amd first"<<endl;
cout<<input<<endl;
}
void a(const string &input)
{
cout<<"I amd second"<<endl;
cout<<input<<endl;
}
int main( )
{
a("str"); // call void a(bool input)
a(string("str")); //call void a(const string &input)
return 0;
}
"str" is of type const char[4], which decays immediately to const char *, and the conversion from any pointer type to bool is considered before non-explicit constructors to custom types.
So, I'd say that the answer is "because the standard says so".
The relevant passage should be 13.3.3.2 ¶2:
When comparing the basic forms of implicit conversion sequences (as defined in 13.3.3.1)
a standard conversion sequence (13.3.3.1.1) is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence [...]
I'd guess it is because when you call a("str"), you're trying to call a function with parameters const char *. It will convert any type of pointer to bool before any other implicit conversion (::std::string etc).
I would like to prefer a certain implicit conversion sequence over another. I have the following (greatly simplified) class and functions:
class Whatever {...}
template <class T>
class ref
{
public:
operator T* ()
{
return object;
}
operator T& ()
{
return *object;
}
T* object;
...
};
void f (Whatever*)
{
cout << "f (Whatever*)" << endl;
}
void f (Whatever&)
{
cout << "f (Whatever&") << endl;
}
int main (int argc, char* argv[])
{
ref<Whatever> whatever = ...;
f(whatever);
}
When I have a ref object and I am making an ambiguous call to f, I would like the compiler to choose the one involving T&. But in other unambiguous cases I wish the implicit conversion to remain the same.
So far I have tried introducing an intermediate class which ref is implicitly convertible to, and which has an implicit conversion operator to T*, so the conversion sequence would be longer. Unfortunately it did not recognize in unambiguous cases that it is indeed convertible to T*. Same thing happened when the intermediate class had a(n implicit) constructor. It's no wonder, this version was completely unrelated to ref.
I also tried making one of the implicit conversion operators template, same result.
There's no "ranking" among the two conversions; both are equally good and hence the overload is ambiguous. That's a core part of the language that you cannot change.
However, you can just specify which overload you want by making the conversion explicit:
f((Whatever&) whatever);
Simple: define void f(const ref<Whatever>&), it will trump the others which require a conversion.
Only one user-defined conversion function is applied when performing implicit conversions. If there is no defined conversion function, the compiler does not look for intermediate types into which an object can be converted.