Is there a way to disable conversion operators? Marking them "= delete" messes up other things.
Consider the following code:
class Foo
{
public:
Foo() :mValue(0) {}
~Foo() = default;
Foo(int64_t v) { mValue = v; }
Foo(const Foo& src) = default;
bool operator==(const Foo& rhs) { return mValue == rhs.mValue; }
/* after commenting these lines the code will compile */
operator int() const = delete;
operator int64_t() const = delete;
private:
int64_t mValue;
};
int main()
{
Foo foo1(5);
Foo foo2(10);
bool b1 = (foo1 == foo2);
bool b2 = (foo1 == 5);
}
This won't compile because gcc complains that the == operator is ambiguous:
test.cc: In function ‘int main()’:
test.cc:25:21: error: ambiguous overload for ‘operator==’ (operand types are ‘Foo’ and ‘int’)
bool b2 = (foo1 == 5);
^
test.cc:25:21: note: candidates are:
test.cc:25:21: note: operator==(int, int) <built-in>
test.cc:25:21: note: operator==(int64_t {aka long int}, int) <built-in>
test.cc:10:10: note: bool Foo::operator==(const Foo&)
bool operator==(const Foo& rhs) { return mValue == rhs.mValue; }
^
However, after commenting the conversion operators, the code will compile and run nicely.
The first question is: why do the deleted conversion operators create an ambiguity for the == operator? I thought they should disable implicit Foo -> int conversions but they seem to affect int -> Foo conversions which does not sound logic to me.
Second one: is there a way to mark the conversion operators deleted? Yes, by not declaring them - but I'm looking for a way that anyone in the future will see that those conversions are disabled by design.
Any use of a deleted function is ill-formed (the program will not
compile).
If the function is overloaded, overload resolution takes place first,
and the program is only ill-formed if the deleted function was
selected.
In you case programm can't select conversion becouse you have 3 variant
int -> Foo
Foo -> int
Foo -> int64
Second question:
you can leave it as it is, but always use explicit conversion for int
bool b2 = (foo1 == Foo(5));
Here's what I think is the crux of the matter:
[dcl.fct.def.delete]:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
...
A deleted function is implicitly an inline function ([dcl.inline]).
[class.member.lookup/4]:
If C contains a declaration of the name f, the declaration set
contains every declaration of f declared in C that satisfies the
requirements of the language construct in which the lookup occurs.
Even if you delete the function, you still declare it. And a declared function will participate in overload resolution. It's only when it's the resolved overload, that the compiler checks if it's deleted.
In your case, there is an obvious ambiguity when those function declarations are present.
Related
#include <type_traits>
#include <iostream>
template<typename T>
struct Wrapper {
T value;
operator T&() & { std::cout << "call const ref" << std::endl; return this->value; }
operator const T&() const& { std::cout << "call const ref" << std::endl; return this->value; }
operator T&&() && { std::cout << "call move" << std::endl; return std::move(this->value); }
operator const T&() const&& = delete;
operator T&&() & = delete;
operator T&&() const& = delete;
};
class A {
public:
A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
};
int main() {
Wrapper<A> b;
A bb;
bb = std::move(b);
}
I compiled this code with gcc10.2, and get the following error
test.cc: In function ‘int main()’:
test.cc:24:21: error: ambiguous overload for ‘operator=’ (operand types are ‘A’ and ‘std::remove_reference<Wrapper<A>&>::type’ {aka ‘Wrapper<A>’})
24 | bb = std::move(b);
| ^
test.cc:17:12: note: candidate: ‘A& A::operator=(const A&)’
17 | A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
| ^~~~~~~~
test.cc:18:12: note: candidate: ‘A& A::operator=(A&&)’
18 | A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
| ^~~~~~~~
But I tried the same code with clang in cppinsights.io, and compiled successfully.
link
So, what cause the difference between gcc and clang?
And how do I change the Wrapper to fix it?
https://godbolt.org/z/37dPqafGK
Quoted text is from the C++20 standard, but links are to N4861.
First let's minimize the given code. The results with GCC 11.2 and Clang 12.0.1 are as indicated in the question (Clang accepts, GCC rejects) if three of the conversion functions are deleted, leaving only:
operator const T&() const&; // not deleted
operator T&&() &&; // not deleted
operator const T&() const&& = delete;
From this we can infer that Clang only accepts the code because, in trying to form an implicit conversion sequence that would enable A& A::operator=(const A&) to be called, it selects the deleted Wrapper<A>::operator const T&() const&& to perform the conversion to const A&. Thus, forming the implicit conversion sequence fails, and A& A::operator=(const A&) is not viable, leaving only the other candidate.
The divergence between GCC and Clang appears to be related to CWG issue 2525. The issue is as follows: it appears that the intent of Note 1 to [over.ics.best.general]/2 ([over.ics.best]/2 in N4861) is that if an implicit conversion sequence involves a deleted function, the implicit conversion sequence is still considered to be formed, and only if the candidate that requires this implicit conversion sequence is selected, then the program is ill-formed since it requires a call to the deleted function. But the normative wording does not seem to reflect this intention.
GCC and Clang agree that the declaration const A& t = std::move(b); is ill-formed because it invokes the deleted function Wrapper<A>::operator const A&() const&&. Thus, Clang appears to be following the normative wording as it currently exists: since the initialization const A& t = std::move(b) would be ill-formed, it follows that std::move(b) is not implicitly convertible to const A& under [conv]/3, and thus the implicit conversion sequence required to make the copy assignment operator a viable candidate does not exist, and only the move assignment operator is viable according to Clang. But GCC is following what the note seems to be telling us: to ignore (at the overload resolution stage) the fact that one of the implicit conversion sequences contains a deleted function. This ultimately results in GCC being unable to decide whether to call the copy assignment operator or the move assignment operator.
It looks like the problem is that you have two overloads. There's the overload resolution of A::operator=, but there is also the overload resolution of the Wrapper<A> conversion operator in the conversion sequence. C++ cannot resolve multiple overloads simultaneously.
For a simpler case, imagine if A::operator= was overloaded for int and float, and Wrapper<A> defined both operator int and operator float.
This is of course only the case if there is an overload, i.e. there are multiple candidate conversion operators in Wrapper<A>. But I don't see a reason why clang should exclude 2 out of the 3 conversion operators.
I also don't seen an obvious fix. The problem itself looks ambiguous to me.
The following won't compile:
#include <memory>
class A;
bool f() {
std::shared_ptr<A> a;
return a;
}
int main()
{
f();
return 0;
}
and fails with:
Compilation failed due to following error(s).main.cpp: In function ‘bool f()’:
main.cpp:13:12: error: cannot convert ‘std::shared_ptr’ to ‘bool’ in return
return a;
What could be the reasoning of the standard (I presume) not allowing an implicit conversion here?
Because the user-defined operator for converting an std::shared_ptr to bool is explicit:
explicit operator bool() const noexcept;
Note that an implicit conversion to bool in the condition of an if statement – among others – still happens even with an explicit user-defined conversion operator to bool :
std::shared_ptr<int> ptr;
if (ptr) { // <-- implicit conversion to bool
}
That is, you don't need to write static_cast<bool>(ptr) in the condition of an if statement.
Given a simple class template with multiple implicit conversion functions (non-explicit constructor and conversion operator), as in the following example:
template<class T>
class Foo
{
private:
T m_value;
public:
Foo();
Foo(const T& value):
m_value(value)
{
}
operator T() const {
return m_value;
}
bool operator==(const Foo<T>& other) const {
return m_value == other.m_value;
}
};
struct Bar
{
bool m;
bool operator==(const Bar& other) const {
return false;
}
};
int main(int argc, char *argv[])
{
Foo<bool> a (true);
bool b = false;
if(a == b) {
// This is ambiguous
}
Foo<int> c (1);
int d = 2;
if(c == d) {
// This is ambiguous
}
Foo<Bar> e (Bar{true});
Bar f = {false};
if(e == f) {
// This is not ambiguous. Why?
}
}
The comparison operators involving primitive types (bool, int) are ambiguous, as expected - the compiler does not know whether it should use the conversion operator to convert the left-hand template class instance to a primitive type or use the conversion constructor to convert the right-hand primitive type to the expected class template instance.
However, the last comparison, involving a simple struct, is not ambiguous. Why? Which conversion function will be used?
Tested with compiler msvc 15.9.7.
According to [over.binary]/1
Thus, for any binary operator #, x#y can be interpreted
as either x.operator#(y) or operator#(x,y).
According to this rule, in the case of e == f, the compiler can only interpret it as e.operator==(f), not as f.operator==(e). So there is no ambiguity; the operator== you defined as a member of Bar is simply not a candidate for overload resolution.
In the case of a == b and c == d, the built-in candidate operator==(int, int) (see [over.built]/13) competes with the operator== defined as a member of Foo<T>.
Operator overloads implemented as member functions don't allow for implicit conversion of their left-hand operand, which is the object on which they are called.
It always helps to write out the explicit call of an operator overload to better understand exactly what it does:
Foo<Bar> e (Bar{true});
Bar f = {false};
// Pretty explicit: call the member function Foo<Bar>::operator==
if(e.operator ==(f)) { /* ... */ }
This can't be confused with the comparison operator in Bar, because it would require an implicit conversion of the left-hand side, which is impossible.
You can trigger an ambiguity similar to the ones you see with the built-in types when you define Bar and its comparison operator like this:
struct Bar { bool m; };
// A free function allows conversion, this will be ambiguous:
bool operator==(const Bar&, const Bar&)
{
return false;
}
This is nicely demonstrated and explained in Scott Meyers's Effective C++, Item 24.
Our code is multi-platform. I use MSVC and my colleagues use Clang or GCC.
One of our classes has a pointer conversion, which always returns non-nullptr. I found a problem due to to accidentally using it in boolean expressions, which would always return true. So, I deleted the boolean conversion operator.
This worked perfectly for me (and actually revealed ten other places in our program where we were doing this), but my colleagues could no longer compile due to ambiguous overloads.
Here's the situation:
class Thingy
{
public:
operator const double * () const
{
return xyz;
}
operator bool () const = delete;
private:
double xyz[3];
};
void func(const double *d)
{
}
void func(unsigned int i)
{
}
int main()
{
Thingy a;
func(a);
}
On MSVC, func(const double *d) is called, which I would expect. However, on GCC, it fails as follows:
<source>: In function 'int main()':
<source>:27:9: error: call of overloaded 'func(Thingy&)' is ambiguous
func(a);
^
<source>:16:6: note: candidate: 'void func(const double*)'
void func(const double *d)
^~~~
<source>:20:6: note: candidate: 'void func(unsigned int)'
void func(unsigned int i)
^~~~
Compiler returned: 1
If you take out the operator bool () const = delete;, it works fine on GCC, but you then don't have protection from the following:
Thingy b /* = callSomeFunction()*/;
if (!b)
{
// whoops! never enters here! Would be nice for this to be a compiler error.
}
else
{
// do something with the valid Thingy
}
We think that on GCC/Clang the ambiguity is between calling func(const double*) with the conversion to const double* and calling func(unsigned int) with a conversion to bool, even though the conversion to bool is explicitly invalid.
Why is this the case? Why does MSVC allow it? Is there any way we can get the desired effect, other than perhaps #ifdef _MSVC_VER?
The simplest solution would be to declare the operator bool as explicit. Used as a condition in a if, while or for, the explicit operator would be called, but ignored for implicit conversions on function calls.
http://coliru.stacked-crooked.com/a/78dd309602295e79
I am studying converting constructors and conversion operators in C++.
What I've learned so far is that any non-explicit constructor that takes only one parameter (and any number of optional default arguments) represents an implicit class-type conversion to THAT class type, for example if a class defines a constructor that has one parameter of type int I can use an int wherever an object of that class type is required:
(assuming class_type has an overloaded += operator)
class_type a;
a+=5;
in this case 5 is implicitly converted (through the converting constructor) to class_typeand the overloaded operator is called.
Now, the (at least for me) tricky part: I know I can define a conversion operator as a member function :
operator int() {....};
that converts the object of class_type to the primitive int type, and I can use that conversion like:
class_type a;
a+5;
in this case I've read that the object is converted to an int through its conversion operator and then the buil-in sum operator is called.
But what if I defined an overloaded + operator to take two class_type objects as its arguments? something like
class_type operator+(const class_type&,const class_type &c);
how is the compiler supposed to know which one to call through function matching?
does the conversion to int only happens implicitly when only the built-in operator is defined?
thanks!
edit:
actually,I've tried to write some code to effectively try it out, it turned out that my compiler (g++) doesn't issue any ambiguous call error!
this is the class header (along with the non-memeber operator+ function declaration) :
#include <iostream>
class wrapper {
friend std::ostream &operator<<(std::ostream&,const wrapper&);
public:
wrapper()=default;
wrapper(int);
int get();
operator int() const;
wrapper operator+(int);
private:
int a=10;
};
std::ostream &operator<<(std::ostream&,const wrapper&);
and this is the main code:
#include "wrapper.h"
int main()
{
using namespace std;
wrapper w1;
wrapper w2(5);
cout<<w1<<" "<<w2<<endl;
w1+1;
}
now,I've defined a converting constructor from int to wrapper AND a conversion operator from class type to int(I've also overloaded the << output operator in order to print some results), but when the compiler evaluates the expression w1+1 it seems to be fine. How could it possibly be??
If you have for example the following class declaration that contains a conversion constructor and a conversion operator
struct A
{
A( int x ) : x( x ) {}
operator int() const { return x; }
int x;
};
const A operator +( const A &a1, const A &a2 )
{
return A( a1.x + a2.x );
}
then statement
a1 + a2;
where a1 and a2 are declared like for example
A a1( 10 );
A a2( 20 );
will be well-formed because there is no need to call a conversion function. The both operands match the parameter declarations of the operator +.
However if you will write for example
a1 + 20;
when the compiler issues an error because there is an ambiguity. The compiler can either apply conversion constructor A( int ) to convert the second operand to type A and call the operator defined for objects of type A. Or it can apply the conversion operator operator int to convert the first operand to type int and call the built-in operator + for objects of type int.
To avoid this ambiguity you could declare either the constructor or the operator (or the both) with function specifier explicit.
For example
explicit A( int x ) : x( x ) {}
or
explicit operator int() const { return x; }
In this case only one implicit conversion would exist and there was not an ambigiuty.
I would like to append the above description that sometimes some converion operators can be called implicitly even if they are declared with the function specifier explicit.
For example According to the C++ Standard (6.4 Selection statements)
...The value of a condition that is an expression is the value of the
expression, contextually converted to bool for statements other
than switch;
and (5.16 Conditional operator)
1 Conditional expressions group right-to-left. The first expression is
contextually converted to bool (Clause 4).
So for example if the above class has the following conversion operator declared with the function specifier explicit
explicit operator bool() const { return x != 0; }
nevertheless it will be called implicitly for example in the following statement
A a( 10 );
std::cout << ( a ? "true" : "false" ) << std::endl;
Here a will be converted to an object of type bool in the conditional operator.
EDIT: After you updated your question this expression
w1+1;
is an exact match for operator
wrapper operator+(int);
Neither conversion are required. So the code compiles successfully.
This is something you can easily try and see what the compiler does:
#include <iostream>
struct ABC {
int v;
ABC(int x) : v(x) { }
operator int() const { return v; }
void operator +=(ABC const &that) {
v += that.v;
}
};
ABC operator+(ABC const &lhs, ABC const &rhs) {
return { lhs.v + rhs.v };
}
int main() {
ABC a(5);
std::cout << a + 1 << '\n';
a += 10;
std::cout << a << '\n';
}
what if I defined an overloaded + operator to take two class_type objects as its arguments?
GCC
error: ambiguous overload for 'operator+' (operand types are 'ABC' and 'int')
The compiler sees two candidates: operator+(int, int) <built-in> and ABC operator+(const ABC&, const ABC&). This means it could implicitly convert not only the 5 in a + 5 to a but also the a to int. Post these conversions both operator+ functions become potential matches.
How is the compiler supposed to know which one to call through function matching?
It doesn't know hence the error.
does the conversion to int only happens implicitly when only the built-in operator is defined?
Yes, otherwise it doesn't automatically convert class_type to int. However, int to class_type would happen implicitly unless you make class_type's constructor explicit:
explicit ABC(int x) : v(x) { }
If you've access to C++11, then you also make the conversion function explicit:
explicit operator int() const { return v; }