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.
Therefore a good rule of thumb says that, whenever you're writing constructors that can be called with just one argument (note that this one foo (int i, bool b = false) can be called with one argument, too, even though it takes two arguments), you should make that constructor explicit, unless you really want implicit conversion to kick in.
If you have constructors which can be invoked with just one argument, you effectively created an implicit conversion operator. In your example, wherever a CFixed is needed, both an int and a float can be passed.
These constructors allow you to initialize a class value without specifying the name of the class. Let's look at an example: Wait, is the class MyClass now the integer type? No. What actually happened was that the C++ compiler was able to tell that you were calling the MyClass constructor implicitly and allowed that conversion.
I hope you learned something today, and have good day! Yes C++ has always allowed implicit "casting" of values. If a suitable constructor has been found. That is both a blessing and a curse. Of course it's up to you to design in what your class should or should not have.
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;
}
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