11

I don't understand why the code below prints struct Value instead of int (which implies the conversion constructor is converting to Value instead of int). (Visual C++ 2012)

Why is this happening? Why does the compiler completely ignore the Value(int) constructor?

#include <iostream>
#include <type_info>

using namespace std;

struct Value { Value(int) { } };

struct Convertible
{
    template<class T>
    operator T() const
    { throw typeid(T).name(); }
};

int main()
{
    try { Value w((Convertible())); }
    catch (char const *s) { cerr << s << endl; }
}

Edit:

Even more bizarre is this (this time it's C++11 only, on GCC 4.7.2):

#include <iostream>
#include <typeinfo>

using namespace std;

struct Value
{
    Value(Value const &) = delete;
    Value(int) { }
};

struct Convertible
{
    template<class T>
    operator T() const
    { throw typeid(T).name(); }
};

int main()
{
    try { Value w((Convertible())); }
    catch (char const *s) { cerr << s << endl; }
}

Which gives:

source.cpp: In function 'int main()':
source.cpp:21:32: error: call of overloaded 'Value(Convertible)' is ambiguous
source.cpp:21:32: note: candidates are:
source.cpp:9:3: note: Value::Value(int)
source.cpp:8:3: note: Value::Value(const Value&) <deleted>

If the copy constructor is deleted, then why is there any ambiguity?!

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 4
    Why would it print `int`? You are trying to create an instance of `Value`, so you get a conversion to `Value`. – R. Martinho Fernandes Dec 19 '12 at 10:44
  • @R.MartinhoFernandes: I'm calling `Value`'s constructor, and its argument is of type `int`, so why isn't `Convertible()` being converted to `int`? Why is the implicit copy constructor completely hiding my explicitly-defined constructor, instead of causing an ambiguity error? – user541686 Dec 19 '12 at 10:46
  • http://liveworkspace.org/code/3ZUTMY$0 ... which compiler you use? – ForEveR Dec 19 '12 at 10:46
  • @Mehrdad it's going to be converted to `int` and then to `Value`. – Tony The Lion Dec 19 '12 at 10:46
  • @ForEveR: Visual C++... I wonder if this is a compiler bug? – user541686 Dec 19 '12 at 10:47
  • @TonyTheLion: If it's indeed going to be converted to `int` as you say, then I should be seeing `int`, not `struct Value`, on stderr. – user541686 Dec 19 '12 at 10:48
  • @R.MartinhoFernandes: I'm not sure if this is related, but see my update. – user541686 Dec 19 '12 at 10:55
  • 1
    I understand the question now, but what you call "bizarre" is normal: deleted functions are not removed from overload resolution. They simply make the program ill-formed if they are selected. This a feature. – R. Martinho Fernandes Dec 19 '12 at 10:56
  • @R.MartinhoFernandes: Okay, I do find that bizarre, but if that's indeed the case, then why didn't they cause ambiguity in the first case? It seems like it's impossible to tell the compiler to pick a non-copy constructor... is that a feature or a bug? And why is it inconsistent? – user541686 Dec 19 '12 at 10:57
  • @Mehrdad with GCC there is, even in the first case. It seems to be a compiler bug. – R. Martinho Fernandes Dec 19 '12 at 10:58
  • @R.MartinhoFernandes: Huh... is it even *possible* to call a constructor other than the copy ctor from a templated conversion operator? – user541686 Dec 19 '12 at 10:59
  • https://connect.microsoft.com/VisualStudio/feedback/details/774730/templated-conversion-constructor-always-calls-copy-constructor-even-when-ambiguous – user541686 Dec 19 '12 at 11:05
  • @R.MartinhoFernandes: Seems to be a feature, not a bug! See the Connect link above. – user541686 Dec 22 '12 at 22:38

2 Answers2

8

In the first example Visual Studio is incorrect; the call is ambiguous. gcc in C++03 mode prints:

source.cpp:21:34: error: call of overloaded 'Value(Convertible)' is ambiguous
source.cpp:21:34: note: candidates are:
source.cpp:9:5: note: Value::Value(int)
source.cpp:6:8: note: Value::Value(const Value&)

Recall that a copy constructor is implicitly defaulted. The governing paragraph is 13.3.1.3 Initialization by constructor [over.match.ctor]:

When objects of class type are direct-initialized [...], overload resolution selects the constructor. For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized.

In the second example, deleted functions participate equally in overload resolution; they only affect compilation once overloads have been resolved, when a program that selects a deleted function is ill-formed. The motivating example in the standard is of a class that can only be constructed from floating-point types:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};
ecatmur
  • 152,476
  • 27
  • 293
  • 366
1

I've tried your code (on the Visual Studio version only).

Since you have a built-in copy-CTOR, I guess your main is equal to:

int main()
{
    try { Value w((struct Value)(Convertible())); }
    catch (char const *s) { cerr << s << endl; }
}

The compiler has chosen to use your copy CTOR, rather than Value(int).

Changing it to:

int main()
{
    try { Value w((int)(Convertible())); }
    catch (char const *s) { cerr << s << endl; }
}

It printed "int".

StackHeapCollision
  • 1,743
  • 2
  • 12
  • 19