Ambiguous overloads, implicit conversion and explicit constructors [duplicate] - c++

This question already has answers here:
Calling an explicit constructor with a braced-init list: ambiguous or not?
(2 answers)
Closed 2 years ago.
Consider the following little program:
#include <vector>
class A {
int a;
int b;
public:
explicit A() = default;
A(int _a, int _b) : a(_a), b(_b) {}
int f(const A& a) { return 0; }
int f(std::vector<int> a) { return 1; }
};
int g(const A& a) { return 2; }
int g(std::vector<int> a) { return 3; }
int main() {
A a(1,2);
// a.f({}); //ambiguous according to gcc
g({}); //ambiguous according to gcc
return 0;
}
GCC 10.2 refuse to compile it: it says the call to g({}) and a.f({}) are ambiguous. Clang compile this without complaining.
It seems to me that g(const A&) shouldn't be considered in overload resolution because implicit conversion from no arguments is not allowed: A::A() is marked explicit.
I am not confident that the fault isn't mine and, in any case, I'd like to find a workaround.
Is there another default generated constructor that could be the source of my problem?
You can try it on the compiler explorer.
This SO post was brought to my attention. Its answer tells us which compiler is right: it's GCC. But it does not tell us how to obtain the desired behavior with those overload rules.

You're right that it seems like a bug. The commented out line below fails to compile for the exact reason that there should be no ambiguity.
Got a workaround for you using std::initializer_list:
#include <fmt/core.h>
#include <vector>
class A {
int a;
int b;
public:
explicit A() = default;
A(int _a, int _b) : a(_a), b(_b) {fmt::print("Aab\n");}
void f(const A& a) { fmt::print("A\n"); }
void f(std::vector<int> a) { fmt::print("vector\n"); }
void f(std::initializer_list<int> l) {
return f(std::vector<int>(l));
}
};
void g(const A& a) { fmt::print("A\n"); }
void g(std::vector<int> a) { fmt::print("vector\n"); }
void g(std::initializer_list<int> a) {return g(std::vector<int>(a)); }
int main() {
A a(1,2);
A a2 = A();
//A a3 = {};
a.f({});
g({});
return 0;
}

Related

User defined conversion assigned to const ref variable via temporary object

The code below is a simplified version of the actual problem I am facing.
Assume I do not have permission to modify class A (as it is external library), and its already widely used in my existing code base.
The const & assignment from a temporary object (direct constructor) which also return a const & member variable via implicit conversion is not valid in this case.
How do I prevent or make it legal in this case so that the caller gets the correct A value?
class A
{
public:
A() { }
A(int _r, int _g, int _b)
: r(_r), g(_g), b(_b)
{
}
~A(){ }
int GetR() const { return r; }
int GetG() const { return g; }
int GetB() const { return b; }
private:
int r = 0;
int g = 0;
int b = 0;
};
class Foo
{
public:
Foo() : Foo(A()) {}
Foo(int _r, int _g, int _b) : a(A(_r, _g, _b)) {}
explicit Foo(const A& _a) : a(_a) {}
Foo& operator=(const A& a)
{
*this = Foo(a);
return *this;
}
operator A() const { return a; }
operator const A&() const { return a; }
private:
A a;
};
int main()
{
const A& a = Foo(200, 100, 300);
std::cout << a.GetR() << a.GetG() << a.GetB() << endl; // I may not get 200 100 300 here as Foo is already out of scope
return 0;
}
Motivation
Some background on why I am implementing a class as above. The actual purpose of class Foo is to contain 2 different objects, which actually has the same purpose, just different way of storing data internally. For example, let's say class A and class B, which stores RGB value of color in int and floating (normalized) respectively. And as mentioned above, I do not have permission to modify class A, and its already widely used in my code base.
There are tons of function in my code base which takes in const A& and const B& as a function param. So I am trying to unify this 2 classes for a particular case, where I can just pass in Foo in those places and it will work as expected.
You can apply ref-qualified member functions (since C++11), i.e. mark the conversion operator with lvalue-reference, to prevent it being called on temporaries (rvalues).
class Foo
{
public:
... ...
operator A() const { return a; }
operator const A&() const & { return a; }
operator const A&() && = delete;
... ...
};
Then
const A& a = Foo(200, 100, 300); // invalid; invokes deleted operator
const A& a = static_cast<A>(Foo(200, 100, 300)); // fine; invokes operator A()

C++ compilation error: cannot convert from B to A, no constructor, or constructor overload ambiguity

I have some code which implies a type conversion, which does not compile although there is a conversion method...
class A
{
public:
A(void) :_m(0) { }
A(int val) : _m(val) {}
private:
int _m;
};
class B
{
public:
B(void) : _m(0) {}
B(int val) : _m(val) {}
B(const A&);
// there is a direct conversion operator here
operator A(void) const { return A(_m); }
operator int(void) const { return _m; }
private:
int _m;
};
int main()
{
B b;
A a = (A)b; // error C2440 here
}
Here is the error message:
error C2440: 'type cast': cannot convert from 'B' to 'A'
message : No constructor could take the source type, or constructor overload resolution was ambiguous
The error message means that these two operators
operator A(void) const { return A(_m); }
operator int(void) const { return _m; }
can be used in the expression
(A)b;
As a result using these conversion operators there can be used either the constructor A( int ) or the default copy constructor A( const A & ).
To make it more clear rewrite the corresponding declaration like
A a = A( b );
So whether the object b is converted to an object of the type A using the first conversion operator or to an object of the type int using the second conversion operator.
You could avoid the ambiguity declaring the operators for example like
operator A(void) const & { return A(_m); }
operator int(void) const && { return _m; }
that is for lvalues the first operator will be used and for rvalues the second operator will be used.
Here is your program with the modified operators.
#include <iostream>
class A
{
public:
A(void) :_m(0) { }
A(int val) : _m(val) {}
private:
int _m;
};
class B
{
public:
B(void) : _m(0) {}
B(int val) : _m(val) {}
B(const A&);
// there is a direct conversion operator here
operator A(void) const & { return A(_m); }
operator int(void) const && { return _m; }
private:
int _m;
};
int main()
{
B b;
A a = b;
A a1 = B();
}
From what I understand, the compiler tries several paths to interpret a = (A)b.
it finds the operator A
but it also finds the operator int on B, and the A(int) constructor which gives it a second path B => int => A...
And it does not know which to pick.
To fix the compilation, I can:
remove the operator int from B
rewrite the error line as A a = b.operator A();...

C++ error in gcc and sucess VS

The code below not compile in gcc 4.7, but compile in VS(9, 10, 11) also follows the gcc output.
#include <iostream>
using namespace std;
class A
{
public:
virtual void M() = 0;
};
class B
{
public:
inline B& operator<<(A &value)
{
value.M();
return *this;
}
};
class C: public A
{
public:
virtual void M()
{
cout << "Hello World" << endl;
}
};
int main()
{
B b;
C c;
b << c; //line not erro
b << C(); //Line with error
return 0;
}
gcc log
$g++ main.cpp -o test main.cpp: In function 'int main()':
main.cpp:36:12: error: no match for 'operator<<' in 'b << C()'
main.cpp:36:12: note: candidates are: main.cpp:14:15: note: B&
B::operator<<(A&) main.cpp:14:15: note: no known conversion for
argument 1 from 'C' to 'A&'
C++ does not allow you to bind a non-const reference to a temporary like you are attempting to do here:
b << C();
// ^^^^ temporary
VS allows you to do this as an "extension", but it is non-standard and therefore non-portable, as you have discovered.
What you need is a const reference in the relevant operator:
inline B& operator<<(const A& value)
// ^^^^^
One way of sorting it out is using C++11 rvalue binding feature.
B& operator<<(A&& value)
{ ... }
as an overload.

Interconvertible types and ambiguous calls

I have a sequence of types, which I want to be freely convertible to one another. Consider the following toy example:
struct A {
int value;
A(int v) : value(v) { }
};
struct B {
int value;
B(int v) : value(v) { }
B(A a) : value(a.value) { }
operator A() const { return A(value); }
};
struct C {
int value;
C(int v) : value(v) { }
C(A a) : value(a.value) { }
C(B b) : value(b.value) { }
operator B() const { return B(value); }
operator A() const { return A(B(*this)); } // <-- ambiguous
};
int main(int argc, const char** argv) {
C c(5);
A a(3);
a = c;
}
So as you see, I'm trying to defined each subsequent type to be convertible from all previous types using cast constructors, and to be convertible to all previous types using cast operators. Alas, this does not work as intended, as the definition of C::operator A is ambiguous according to gcc 4.7:
In member function ‘C::operator A() const’:
19:40: error: call of overloaded ‘B(const C&)’ is ambiguous
19:40: note: candidates are:
9:3: note: B::B(A)
6:8: note: constexpr B::B(const B&)
6:8: note: constexpr B::B(B&&)
Changing the expression to static_cast<A>(static_cast<B>(*this)) doesn't change a thing. Removing that line altogether results in an error message in main, as no implicit conversion sequence may use more than one user-defined conversion. In my toy example, I could perform the conversion from C to A direcly, but in my real life application, doing so would cause a lot of duplicate code, so I'd really like a solution which reuses the other conversion operators.
So how can I obtain a set of three freely interconvertible types without duplicating conversion code?
I'd try this way in struct C:
operator A() const { return this->operator B(); }
Try this:
operator A() const { return A(B(value)); }
or this:
operator A() const { return A(operator B()); }

Passing a const reference down a chain of constructors

I get a runtime error with the following code, which is a reproducible reduction of my actual code. I am sure I am not instantiating something properly, but I cannot figure out what it is.
#include <iostream>
using namespace std;
class A {
int n;
public:
A();
A(const int k);
int getn() const { return n; };
};
A::A() : n(0) {};
A::A(const int k) : n(k) {}
class B {
const A& myA;
public:
B(const A& anA);
int getn() const { return myA.getn(); };
};
B::B(const A& anA) : myA(anA) {}
class C {
const A& myA;
const B& myB;
public:
C(const A& anA);
int getn() const { return myB.getn(); };
};
C::C(const A& anA) : myA(anA), myB(myA) {}
class D {
A myA;
C myC;
public:
D(const int k);
int getAn() const { return myA.getn(); };
int getCn() const { return myC.getn(); };
};
D::D(const int k) : myA(k), myC(myA) {}
int main() {
D myD(10);
cerr << "A: " << myD.getAn() << '\n';
cerr << "C: " << myD.getCn() << '\n';
}
I either get a segmentation fault on the second line of output or "C:0", instead of "C:10" which I expect.
The problem is in this line:
C::C(const A& anA) : myA(anA), myB(myA) {}
myB is a reference. But to what? The answer is to a temporary. myB(myA) will construct a temp object that is assigned to your reference. Unfortunately this object will be destroyed after the Ctor exits.
Change your code to the following:
class C {
const A& myA;
const B myB;
public:...
and it should work.
BTW: I nearly always declare constructors with one argument as explicit. Do so and the compiler will warn you.
You are trying to initialize D::myC with reference to D::myA, this is not correct because object is not completely constructed at this moment.
D::D(const int k) : myA(k), myC(myA) {}