7

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?

In silico
  • 51,091
  • 10
  • 150
  • 143
Ron_s
  • 1,429
  • 1
  • 14
  • 24

5 Answers5

7

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.

unkulunkulu
  • 11,576
  • 2
  • 31
  • 49
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Still, a warning would be great here :) Might be difficult to generalize though, and potentially expensive (especially as classes definitions are parsed for each TU the classes are included in...). – Matthieu M. Sep 01 '11 at 07:43
  • I actually do get a warning in VS2005 - "multiple default constructors"... You can of course use pragmas for the specific warning to treat them like errors. – Luchian Grigore Sep 01 '11 at 08:40
2

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • What is the difference between potentially ambiguous and really ambiguous? I would say the original declaration IS ambiguous. Period. – Giorgio Sep 01 '11 at 07:13
  • @Giorgio: "Potentially ambiguous" declaration is a declaration that can be later referred to in such a way, that it will trigger ambiguity error. "Really ambiguous" is a property of that reference. If it triggers the ambiguity error, it is "really ambiguous". – AnT stands with Russia Sep 01 '11 at 07:23
  • What if there cannot possibly exist any reference that does not trigger the ambiguity? – Giorgio Sep 01 '11 at 07:44
  • @Giorgio: such as? If two functions do have the exact same name and signature, you're probably violating the One Definition Rule. – MSalters Sep 01 '11 at 07:52
  • @MSalters: Such as the original example: the default constructor A() can never be invoked because the invocation syntax cannot be distinguished from A(int x = 0) when we use the default value for x. Apparently you have different signatures A(), A(int x = 0) but, in fact, A(int x = 0) implicitly defines two functions: A(int x) and A() in which x is just an initialized local variable. The second of these two functions has the same signature as A(). That's why you will _always_ have a compilation error when you try to instantiate using the A() constructor. – Giorgio Sep 01 '11 at 08:10
  • @Giorgio: In that case, `A(5)` is still unambiguous. That's a "reference" which doesn't trigger the ambiguity. Your question wass specifically about situations in which _all_ expressions would be ambiguous. – MSalters Sep 01 '11 at 08:13
  • I meant all the references to the default constructor A(). Basically you can declare a constructor that you cannot invoke: this seems pretty useless to me and I would forbid it. – Giorgio Sep 01 '11 at 08:15
  • @Giorgio: Well, I understand your logic. However, the language does not require compilers to recognize and diagnose situations that fall under your "there cannot possibly exist any reference that does not trigger the ambiguity". I think it is intuitively clear that detecting such situations is a rather difficult computational problem in general case. – AnT stands with Russia Sep 01 '11 at 08:20
  • If you expand the signature A(int x = 0) to the pair A() (int x = 0 is a local variable) and A(int x), then you have a quite easy solution. – Giorgio Sep 01 '11 at 08:22
  • @AndreyT: I also understand your logic: it is easier to implement the compiler treating A() and A(int x = 0) as different signatures. – Giorgio Sep 01 '11 at 11:19
  • @Giorgio: In this specific case it is certainly easy. But in general case it can be very difficult. – AnT stands with Russia Sep 01 '11 at 14:01
2

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!

Marc
  • 408
  • 3
  • 6
  • From the point of view of the signature it is absolutely equivalent to invoke a constructor with no arguments or a constructor with one argument but using the default value for the argument. So IMO the ambiguity is in the signatures already. – Giorgio Sep 01 '11 at 08:37
  • @Giorgio: The signature does not care about default arguments. Think of it in terms of function pointers, the type of the function pointer only includes the actual argument types, not whether or not any of the arguments can be defaulted. – Kerrek SB Sep 01 '11 at 10:31
  • @ Kerrek SB: "The signature does not care about default arguments." I understand this, but I think that maybe it is wrong to define the signature in this way. When I define A(int x = 0) I can invoke it with A(), as if the signature were A() and not A(int x). So it may be useful to consider A(int x = 0) having two signatures: A(int x) and A(). In this way you could detect the clash between the methods A() and A(int x = 0) of the example. – Giorgio Sep 01 '11 at 11:14
  • @Giorgio - I agree that it can be useful to think about it in that way, but that isn't how the compiler thinks about it. The compiler sees a constructor that takes an int and a constructor that takes no arguments. Default arguments don't play into the function overload / function signatures at all, rather, they are applied at call site time. – Marc Sep 01 '11 at 15:16
  • @Giorgio - It may help to consider this example: lets say you have three .cpp files and no header files. In one, you create a function `int Add5(int x) { return x + 5; }`. In the second, you declare Add5 as: `int Add5(int x = 10);` and call it without any arguments, printing out the results. In the third, you declare Add5 as: `int Add5(int x = 20)` and call it without any arguments, printing the results. Add a `main` somewhere and call the two functions that call `Add5`. What happens when you compile the program? If it compiles, what numbers are printed? – Marc Sep 01 '11 at 15:36
1

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.

Giorgio
  • 5,023
  • 6
  • 41
  • 71
  • That's not the same code he posted... Of course it doesn't compile since you're calling the default constructor of A. – Luchian Grigore Sep 01 '11 at 07:05
  • I use Eclipse , g++ and the code compiles great . only when I try to create an instance of "A" (i.e. int main() { A a; return 0; }) then the compiler produces a compilation error (ambiguity) . – Ron_s Sep 01 '11 at 07:06
  • Well of course I tried to compile with a call to a function. If my code does not compile and the original code does compile, then C++'s semantics is really flawed. -5 for C++. – Giorgio Sep 01 '11 at 07:06
  • But it does not make any sense that the original code compiles either, because it defines a constructor that can never be called. This does not make any sense. – Giorgio Sep 01 '11 at 07:08
  • So by that logic, having a private function that is never called internally should also not compile? – Luchian Grigore Sep 01 '11 at 07:13
  • But you can still call the private function from another member function: it is still possible to call it. The default constructor cannot be called in any way, unless you modify the signature of the other constructor or you delete it. It is not the same logic. – Giorgio Sep 01 '11 at 07:16
  • @Giorgio - Actually it _is_ still possible to call the default constructor... though it requires some rather suspect code to do so. Normally you want to declare your classes in a header so that multiple compilation units can share a single definition; however, if you define `A` in a compilation unit and omit the default argument, or omit the constructor with the default argument entirely, code in that compilation unit can call the default constructor. This duplication can be reduced by using the preprocessor. However, you should avoid creating potentially ambigious code where possible. – Marc Sep 01 '11 at 20:02
  • @Marc: It would be great if you could post an example, i.e. class declaration and call to the default constructor. I am curious because I have not fully understood how this works. – Giorgio Sep 02 '11 at 05:29
  • @Giorgio - I created a [pastebin](http://pastebin.com/3TUsLRKG) to demonstrate. In this case, I use the preprocessor to supply an equivalent definition of my class to the code that wants to call the default constructor. Remember `A(int x)` and `A(int x = 33)` declare the same function, the only difference is that if no arguments are provided, once case will cause an error, and the other will supply a default argument. Again, I will say that this sort of code should be seen as highly suspect, but it is possible. – Marc Sep 02 '11 at 07:29
  • @Giorgio - I decided to expand on my answer as well, since how C++ deals with this is fairly relevant to the question, and fun trivia as well. :) – Marc Sep 02 '11 at 08:03
0

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • I thought that the compiler would find the ambiguity even when the user is not creating an A instance . Guess I was wrong . – Ron_s Sep 01 '11 at 07:09
  • I have the error even if I compile with the -c option: so the compiler already rejects it. – Giorgio Sep 01 '11 at 07:09
  • Then what do you think , guys ? depends on the compiler the user is using ? – Ron_s Sep 01 '11 at 07:15
  • @Ron_s: It doesn't depend on the compiler. All compilers will produce error if you write `A a`, and all compilers would not produce error if you write `A a(1)`. – Nawaz Sep 01 '11 at 07:18
  • Of course if I write "A a;" then I'd get a compilation error.I was asking about if I won't create any instance of A .. – Ron_s Sep 01 '11 at 07:20
  • @Ron_s: Is it not obvious from my post and comment that if you don't write `A a`, then you wouldn't get any error? – Nawaz Sep 01 '11 at 07:21
  • Nawaz , yes it is . I just thought that the compiler would think two steps ahead . thank you my friend ! – Ron_s Sep 01 '11 at 07:36