4

I'm seeing some different behavior between g++ and msvc around value initializing non-copyable objects. Consider a class that is non-copyable:

class noncopyable_base
{
public:
    noncopyable_base() {}

private:
    noncopyable_base(const noncopyable_base &);
    noncopyable_base &operator=(const noncopyable_base &);
};

class noncopyable : private noncopyable_base
{
public:
    noncopyable() : x_(0) {}
    noncopyable(int x) : x_(x) {}

private:
    int x_;
};

and a template that uses value initialization so that the value will get a known value even when the type is POD:

template <class T>
void doit()
{
    T t = T();
    ...
}

and trying to use those together:

doit<noncopyable>();

This works fine on msvc as of VC++ 9.0 but fails on every version of g++ I tested this with (including version 4.5.0) because the copy constructor is private.

Two questions:

  1. Which behavior is standards compliant?
  2. Any suggestion of how to work around this in gcc (and to be clear, changing that to T t; is not an acceptable solution as this breaks POD types).

P.S. I see the same problem with boost::noncopyable.

R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
  • 4
    I'm pretty sure MSVC is being non-compliant, lemme look for the quote. (I think the compiler is allowed to elide the copy *as long as* the copy constructor was available.) – GManNickG Apr 19 '10 at 23:29
  • Comeau (http://www.comeaucomputing.com/tryitout/) supports GMan. I have seen a few bugs in this area even with recent VC versions. – sbi Apr 19 '10 at 23:45
  • When you say "work around," would it be okay to have two function templates, one for POD types and one for non-POD types (and use SFINAE to select between them)? – James McNellis Apr 20 '10 at 00:19
  • @James is right; there is no way to have what you want. C++0x would allow `T t{};`, though. – GManNickG Apr 20 '10 at 00:20
  • 2
    Hardly "no way" to have what you want. C++ is pretty doggone powerful. In this case, the workaround isn't even all that evil (although uglier than the original, to be sure). – Ben Voigt Apr 20 '10 at 01:24
  • 2
    That's why I have become so hesitant to say something is impossible with C++... half the time I say it, someone comes along and proves me wrong. – James McNellis Apr 20 '10 at 01:32
  • Ok ok, to be fair I was thinking a direct definition like in the OP. :) – GManNickG Apr 20 '10 at 03:28

4 Answers4

8

The behavior you're seeing in MSVC is an extension, though it's documented as such in a roundabout way on the following page (emphasis mine) http://msdn.microsoft.com/en-us/library/0yw5843c.aspx:

The equal-sign initialization syntax is different from the function-style syntax, even though the generated code is identical in most cases. The difference is that when the equal-sign syntax is used, the compiler has to behave as if the following sequence of events were taking place:

  • Creating a temporary object of the same type as the object being initialized.
  • Copying the temporary object to the object.

The constructor must be accessible before the compiler can perform these steps. Even though the compiler can eliminate the temporary creation and copy steps in most cases, an inaccessible copy constructor causes equal-sign initialization to fail (under /Za, /Ze (Disable Language Extensions)).

See Ben Voigt's answer for a workaround which is a simplified version of boost::value_initialized, as pointed out by litb in a comment to Ben's answer. The docs for boost::value_initalized has a great discussion of the problem, the workaround, and some of the pitfalls of various compiler issues.

Community
  • 1
  • 1
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • Thanks. Although it looks like MSVC has bigger issues. When the private copy constructor and operator= are put in the base class (the way boost::noncopyable is implemented), MSVC always allows equal-sign initialization. Anyone know how to report a bug to the MS compiler team? – R Samuel Klatchko Apr 20 '10 at 00:18
  • 1
    And mention the bug number (or better yet, link) here so we can upvote it. – Ben Voigt Apr 20 '10 at 01:13
  • @BenVoigt - bug # is 552586 (https://connect.microsoft.com/VisualStudio/feedback/details/552586/inherting-noncopyable-base-allows-equal-sign-initialization) – R Samuel Klatchko Apr 20 '10 at 05:44
  • Does the bug actually permit copy-construction when the copy-constructor is inaccessible, or only provide an alternate (non-standard) syntax for construction that avoids "most vexing parse"? I mean, does it only elide the copy-constructor and fail to perform the accessibility check, or does it also copy-construct from an rvalue which is not itself a temporary formed by constructor call? – Ben Voigt Apr 20 '10 at 17:03
  • @BenVoigt - good point. It only allows alternate initialization syntax so it's not completely broken. That said, it does allow for writing non-portable code. – R Samuel Klatchko Apr 20 '10 at 18:08
  • 2
    @BenVoigt - update on the bug report - just got this from MS "I can confirm this is indeed a compiler bug. However, given our schedule and resource constraints, we do not believe this bug is severe enough to warrant fixing at this time. However, we will consider fixing this in a future release. Thank you for taking the time to bring this to our attention! Andy Rich, Visual C++ QA" – R Samuel Klatchko Apr 30 '10 at 19:29
5

I don't think template metaprogamming is needed. Try

template <class T>
void doit()
{
    struct initer { T t; initer() : t() {} } inited;
    T& t = inited.t;
    ...
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Although I "rediscovered" this workaround myself, it turns out that the C++0x draft calls it out pretty clearly, in § 8.5 P 10 there is mention of places where you can use value initialization and the ctor-initializer list is one of them. – Ben Voigt Apr 20 '10 at 02:27
  • Very nice indeed - much simpler and elegant than going down the TMP/SFINAE path that crossed my mind. I wish I could do more than +1. – Michael Burr Apr 20 '10 at 15:08
  • 2
    @Michael, @James, @Ben, This is `boost::value_init`: http://www.boost.org/doc/libs/1_42_0/libs/utility/value_init.htm . Worth noting that C++0x allows `T t{};` – Johannes Schaub - litb Apr 20 '10 at 17:23
3

There's §12.8/14:

A program is ill-formed if the copy constructor or the copy assignment operator for an object is implicitly used and the special member function is not accessible.

And then there's §12.8/15:

When certain criteria are met, an implementation is allowed to omit the copy construction of a class object, even if the copy constructor and/or destructor for the object have side effects.

So, the question is really, if the implementation omits the call to the copy constructor (which it is clearly allowed to do), is the copy constructor actually used?

And, the answer to that is yes, per §3.2/2:

A copy constructor is used even if the call is actually elided by the implementation.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
0

Have you seen what happens when you compile using /Wall with MSVC? It states the following about your class:

nocopy.cc(21) : warning C4625: 'noncopyable' : copy constructor could not be
generated because a base class copy constructor is inaccessible
nocopy.cc(21) : warning C4626: 'noncopyable' : assignment operator could not be
generated because a base class assignment operator is inaccessible

GCC remedy: create a copy constructor for noncopyable (and an assignment operator ideally!) that does what it can to copy the information from noncopyable_base, namely invoking the constructor for noncopyable_base that has no parameters (since that is the only one accessible by noncopyable) and then copying any data from noncopyable_base. Given the definition of noncopyable_base, however, it seems there is no data to copy, so the simple addition of noncopyable_base() to the initializer list of a new noncopyable(const noncopyable &) function should work.

Take note of what MSVC said about your program though. Also note that if you use T t() rather than T t = T(), another warning (C4930) is generated by MSVC, though GCC happily accepts it either way without any warning issued.

Dustin
  • 1,956
  • 14
  • 14
  • 2
    `T t()` is not equivalent due to the most vexing parse; you have to do `T t((T()))`, which should give the same results as `T t = T()`. – James McNellis Apr 20 '10 at 00:25
  • 1
    @Dustin - you're missing the point of `noncopyable_base`. It's designed to make classes noncopyable. See http://www.boost.org/doc/libs/1_42_0/libs/utility/utility.htm#Class_noncopyable – R Samuel Klatchko Apr 20 '10 at 01:19
  • 1
    Happily, the ctor-initializer list isn't subject to the most vexing parse, since the constructor call is the only permitted interpretation. – Ben Voigt Apr 20 '10 at 01:29
  • @Ben: I'd happily believe that - _if_ you can explain why `T f();` can't be interpreted as the declaration of a function `f`, taking no arguments and returning a `T`. – sbi Apr 20 '10 at 07:40
  • @sbi: First you show me a ctor-initializer list that contains `T f();` (other than inside a comment). It actually looks more like... forget it, pasting sample code in a comment doesn't look nice and it's already in my answer. – Ben Voigt Apr 20 '10 at 16:52
  • @James `T t((T()));` and `T t = T();` are not 100% equivalent. The second one won't work with `explicit` copy constructors. – Johannes Schaub - litb Apr 20 '10 at 17:30
  • @Johannes: Thanks for the heads-up; I didn't realize that, but it makes sense. – James McNellis Apr 20 '10 at 19:42
  • @Ben: I'm sorry. I guess this was a brain fart of mine. My code digestion system might not have been functioning properly that day. – sbi Apr 22 '10 at 09:38
  • @sbi: No worries, that happens to everybody now and then. – Ben Voigt Apr 22 '10 at 12:40