Search code examples
c++constructornew-operatorexplicit-constructor

C++ - why does this code compile when there's no obvious constructor match?


Please consider the following code:

class Foo {
 public:
  explicit Foo(double) {}
};

Foo * test();
Foo * test() {
  return new Foo(Foo(1.0));   // (1)
}

My question concerns line (1). This is very similar to a bug that took me some time to track down. I hadn't noticed that the type had been specified twice due to a copy/paste error. The correct line is obviously:

  return new Foo(1.0);

Interestingly, this change also seems to compile warning-free:

  return new Foo(Foo(Foo(Foo(1.0))));

Why do these examples compile without warnings with clang, even with the -Wall -Weverything flags? Why does Foo::Foo(double) accept an instance of Foo as a valid double argument? Is this some idiosyncratic behaviour of operator new?

My original code was in a larger context, and tested with two LLVM-3-based compilers. Both compiled without warnings or errors. With one, the code actually functioned as I expected and in fact I didn't realise there was a bug for some time. With the other, the instance of Foo behaved very strangely - I can't really describe it properly - it was as if a later copy of the returned pointer "magically" became a different value from the original, resulting in mismatched state between two cooperating objects that were supposed to hold equivalent pointers to the shared Foo, but for some reason held different values after assignment. Until I understand what is going on here, it seems truly bizarre!

Interestingly, the following compiles with both compilers:

class Foo { public: explicit Foo(double) {} };
class Bar { public: explicit Bar(double) {} };

Foo * testFoo() { return new Foo(Foo(1.0)); }
Bar * testBar() { return new Bar(Bar(1.0)); }

But the following version does not:

Foo * testFooBar() { return new Foo(Bar(1.0)); }

Solution

  • The compiler automatically generates a copy constructor Foo(const Foo&) and possibly also move constructor depending on exact version/settings Foo(Foo&&). This has nothing to do with operator new, or any pointer magic. Your code simply calls a perfectly normal copy/move constructor defined for you by the compiler. That's all. This behaviour is mandated by Standard. A non-copyable class (at least in the original version of the Standard) is virtually worthless.

    If you don't want automatically generated copy constructors, the usual technique is to define them as deleted or private.

    Also note that the compiler, in some circumstances, has the right to actually eliminate whole objects from your program, even though normally it should not. If you are doing hijinks with pointer-to-Foo set from in a Foo constructor, you must handle them strictly in all constructors and destructors, meaning having to write your own copy/move constructors, destructors, and assignment operators, else you will come a cropper when the compiler elides an object.