Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous overloads, implicit conversion and explicit constructors [duplicate]

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.

like image 945
AFoley Avatar asked Feb 16 '21 16:02

AFoley


People also ask

When to make a constructor explicit or implicit?

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.

What is implicit conversion operator in C++?

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.

What is the purpose of a MyClass constructor?

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.

Does C++ allow implicit casting of values?

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.


Video Answer


1 Answers

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;
}
like image 132
Mark H Avatar answered Oct 08 '22 02:10

Mark H