开发者

Why do these two constructors together not produce an ambiguity error?

开发者 https://www.devze.com 2023-04-02 04:50 出处:网络
Consider the following: class A { private: A() {} public: A(int x = 0) {} }; int main() { A a(1); return 0; } I have two constructors, one is a default and the other one is converting constructor

Consider the following:

class A
{
private:
    A() {}
public:
    A(int x = 0) {}
};


int main()
{
    A a(1);
    return 0;
}

I have two constructors, one is a default and the other one is converting constructor with a default argument. When I try to compile the code, I expected an ambiguity error, but the compiler doesn't produce one.

Even when I don't create an instance of A, it doesn't produce an ambiguity error either.

int main()
{
开发者_开发问答    return 0;
}

Why is that?


There is no compilation error because no error exists in your code. Just like defining two functions: they need to have different signatures, other than that, it's possible. The ambiguity compilation error appears only when you try to call one of those functions. Same will happen trying to call the default constructor of A, even if it is private:

class A
{
private:
    A() {}
public:
    A(int x = 0) {}
    void f() {}
    void f(int x = 0) {}
};

This compiles, trying to call f() with no parameters fails, which makes sense.

Also try:

class A
{
private:
    A() {}
public:
    A(int x = 0) {}
    void f() {}
    void f() const {}
};

Should this give an error? No, because the two f's have different signatures. In this case the compiler can resolve the ambiguity, if you call f on a const object, the const method will get called, and vice-versa.


Your code compiles because there is no ambiguity. You created a class with two constructors, one which always takes 0 arguments, and one which always takes one argument, an int. You then unambiguously called the constructor taking an int value. That this int has a default value does not matter, it is still a completely different signature. That the constructors are potentially ambiguous doesn't matter, the compiler only complains when a particular call is actually ambiguous.

When you create an instance of A with no arguments, it doesn't know which constructor you want to call: the default constructor, or the constructor taking an int with a parameter value of 0. In this case it would be nice if C++ notices that the private constructor is ineligible, but that is not always possible.

This behavior ends up being useful in some circumstances (e.g. if you have a few overloads involving templates, some of which will overlap if given the right types), though for simple cases like this, I would just make the single constructor with the default argument (preferably marked explicit unless you have a really really good reason to leave it implicit, and then I would second guess that reason just to be sure!)

-- EDIT --

Let's have some fun with this and try to explore further what is happening.

// A.h
class A
{
public:
    A(); // declare that somewhere out there, there exists a constructor that takes no args.  Note that actually figuring out where this constructor is defined is the linker's job
    A(int x = 10); // declare that somewhere out there, there exists a constructor that take one arg, an integer. Figuring out where it is defined is still the linker's job. (this line does _not_ declare two constructors.)

    int x;
};

// A.cpp
#include "A.h"

A::A() { ... } // OK, provide a definition for A::A()
A::A(int x) { ... } // OK, provide a definition for A::A(int) -- but wait, where is our default param??

// Foo.cpp
#include "A.h"

void Foo()
{
    A a1(24); // perfectly fine
    A a2; // Ambigious!
}

// Bar.cpp
class A // What's going on? We are redefining A?!?!
{
public:
    A();
    A(int x); // this definition doesn't have a default param!
    int x;
};

void Bar()
{
    A a; // This works! The default constructor is called!
}

// Baz.cpp
class A // What's going on? We are redefining A again?!?!
{
public:
    //A(); // no default constructor!
    A(int x = 42); // this definition has a default param again, but wait, it's different!
    int x;
};

void Baz()
{
    A a; // This works! A::A(int) is call! (but with what parameter?)
}

Note that we are taking advantage of the fact that the compiler doesn't know about headers; by the time it looks at a .cpp file, the preprocessor has already substituted #includes with the body of the header. I am playing at being my own preprocessor, doing some dangerous things like providing multiple, different definitions of a class. Later on, one of the linker's jobs is to throw out all but one of these definitions. If they do not align up in the exact right way, all kinds of bad things will happen, as you will be in the twilight zone of undefined behavior.

Note that I was careful to provide the exact same layout for my class in every compilation unit; every definition has exactly 1 int and 0 virtual methods. Note that I did not introduce any extra methods (though that might work; still doing things like this should be looked on with great suspicion), the only thing that changed were some non-virtual member functions (well constructors really) and then only to remove the default constructor. Changing and removing the default value changed nothing about the definition of A::A(int).

I don't have a copy of the spec on me, so I can't say if my careful changes fall under undefined behavior or implementation specific behavior, but I would treat it as such for production code and avoid leveraging such tricks.

And the ultimate answer to what argument is used inside of Baz is,.... 42!


Declaring potentially ambiguous functions in C++ does not produce any ambiguity errors. Ambiguity takes place when you attempt to refer to these functions in an ambiguous way. In your example ambiguity will occur if you try to default-construct your object.

Strictly speaking, it is perfectly normal to have declarations in C++ program that are potentially ambiguous (i.e. that can be referred to in an ambiguous way). For example, at the first sight these overloaded functions look fine

void foo(double);
void foo(int);

but calling foo(1u) will trigger ambiguity error. So, once again, ambiguity is the property of the way you refer to the previously declared functions, not the property of the function declarations themselves.


Here is a slightly modified example that I have tested with GCC on cygwin:

#include <iostream>

class A
{
  private:
    A();

  public:
    A(int x = 0);
};


A::A()
{
  std::cout << "Constructor 1.\n" << std::endl;
}


A::A(int x)
{
  std::cout << "Constructor 2 with x = " << x << std::endl;
}


int main()
{
  A a1(1);
  A a2;

  return 0;
}

The compiler gives the following error message:

$ g++ test.cc
test.cc: In function `int main()':
test.cc:28: error: call of overloaded `A()' is ambiguous
test.cc:20: note: candidates are: A::A(int)
test.cc:14: note:                 A::A()

Update

I understand how the C++ compiler works (thanks for the explanations given by others): the two declarations are different and therefore accepted, but when trying to reference the default constructor, the compiler cannot decide which of the two it should use.

However, the fact that the default constructor A() can never be invoked can be inferred from the declarations already. Apparently you have two functions with different signatures

A()
A(int x = 0)

but, in fact, A(int x = 0) implicitly defines two functions: A(int x) and A(). In the second function x is just an initialized local variable. Instead of writing

A(int x = 0)
{
   ...
}

you could write two functions:

A(int x)
{
  ...
}

A()
{
  int x = 0;
  ...
}

with the same body. The second of these two functions has the same signature as the default constructor A(). That's why you will always have a compilation error when you try to instantiate class A using the A() constructor. This could be detected even without an explicit declaration such as

A a;

So I totally agree with Ron_s that I would expect an ambiguity error in his example. IMHO it would be more consistent.


Ambiguity doesn't arise because the private constructor is not even considered when you write A a(1), as you pass an argument to it, and the private constructor doesn't take any argument.

However, if you write A a, then there will be ambiguity, because both constructors are candidates, and the compiler cannot decide which one to invoke.

0

精彩评论

暂无评论...
验证码 换一张
取 消