2

In C++, we can assign an object to a non-const reference. So this works:

Foo &foo = Foo();

But, C++ doesn't allow a temporary to be assigned to a non-const reference. So this is not allowed:

Bar::Bar(Foo &foo = Foo()) {}

But this is allowed

Bar::Bar(const Foo &foo = Foo()) {}

I have three questions here:

  1. What is the scope of foo in the last case? Does it live even after the constructor is exited. From what I read, the lifetime of a temporary is modified if it is assigned to a const reference, in which case it takes up the lifetime of the reference is it assigned to. What is the lifetime of the default argument, in this case foo?
  2. I tried the second example in MSVC and it didn't complain. I also noticed that the lifetime of the temporary was still extended. So I could use foo after the constructor exited. What is that about?
  3. My scenario demands the default argument to constructor in this manner and I will need to modify the argument in the constructor (so I cannot make it const). And I also need to use foo after the constructor has exited. What is the best design to cater to this scenario?

Thanks in advance.

Bonton255
  • 2,231
  • 3
  • 28
  • 44
  • 4
    `Foo &foo = Foo();` This certainly does NOT work. – dyp Mar 07 '14 at 19:18
  • 1
    @dyp Unless you're using MSVC. That has an extension which allows temporaries to bind to non-const references. – Angew is no longer proud of SO Mar 07 '14 at 19:20
  • @Angew Yes, use `/Za`. Unfortunately, lots of Microsoft headers won't compile without those extensions. – dyp Mar 07 '14 at 19:23
  • 1
    Related / duplicate: [What is the lifetime of a default argument temporary bound to a reference parameter?](http://stackoverflow.com/q/12554619/420683) – dyp Mar 07 '14 at 19:24
  • By the way, "scope" is the portions of source code where a name can be used to refer to a declaration; "lifetime" is the period of runtime beginning when a constructor finishes and ending when a destructor starts. – aschepler Mar 07 '14 at 20:03

4 Answers4

3

What is the scope of foo in the last case?

foo is a constructor (same applies to regular functions too) argument, so its lifetime ends when the full expression containing the call to the constructor ends. Thanks aschepler!

What is the lifetime of the default argument, in this case foo?

You extended the lifetime of the default argument by binding it to Foo const& foo, so its lifetime will match that of the reference it's bound to, i.e. until the constructor body exits.

I tried the second example in MSVC and it didn't complain.

It does if you set the warning level to /W4; in that case it'll warn you about a non-standard extension being used. AFAIK, the semantics are the same as the previous case.

My scenario demands the default argument to constructor in this manner and I will need to modify the argument in the constructor (so I cannot make it const). And I also need to use foo after the constructor has exited.

It depends on whether you want to save it as member of Bar or not. If it's the former, use an rvalue reference and move the argument

Bar::Bar(Foo&& foo = Foo()) : f_(std::move(foo)) {} // f_ is a member of type Foo

Otherwise, just leave out the default argument. You can also create both overloads to cover different situations.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 1
    The end of the constructor is not the end of the temporary's life: http://stackoverflow.com/questions/12554619 – aschepler Mar 07 '14 at 19:48
  • @aschepler Thanks for pointing that out. But in this case, since it is a constructor, does that apply? I can't think of how you can chain a call to a constructor, or something similar, so you are able to use that reference within an outer expression. – Praetorian Mar 07 '14 at 19:52
1
  1. foo and its temporary are gone after the constructor completes.
  2. It's a MS extension that doesn't follow the language standard.
  3. Don't use temporary default values in this case. If you need to be able to access it after the constructor completes, you need to create the object yourself prior to calling the constructor, and pass that in by reference.
Mark B
  • 95,107
  • 10
  • 109
  • 188
0
  1. In this case (as in most cases), the lifetime of a temporary bound directly to a reference is extended to the lifetime of the reference. So the temporary will be valid as long as foo is, i.e. for the duration of the constructor. It will end when the full-expression containing the constructor call ends.

  2. MSVC has an extension which allows temporaries to bind to non-const references. It's not standard and other compilers don't support it. Don't use this unless you are (and always will be) coding for MSVC only.

  3. That depends on what you actually want to achieve - there's not enough information in your question to answer that. If you need it to persist even after the constructor exits, you need to provide an actual object with appropriate lifetime. You could provide a per-thread static member, for example:

    class Bar
    {
      static thread_local Foo ctor_default_foo;
    
    public:
      Bar(Foo &foo = ctor_default_foo) {}
    };
    

    Note that this of course means that all objects created by one thread using the default argument would share the same Foo object. If you need a new one for each Bar object, you'll have to change your design. But to tell you how, we'd need to know more about the actual use case.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 1
    The end of the constructor is not the end of the temporary's life: http://stackoverflow.com/questions/12554619 – aschepler Mar 07 '14 at 19:48
0

My scenario demands the default argument to constructor in this manner and I will need to modify the argument in the constructor (so I cannot make it const). And I also need to use foo after the constructor has exited. What is the best design to cater to this scenario?

You could make an optional<Foo> inside Bar, as in:

struct Bar
{
    Bar() : optionalFoo(boost::in_place()), foo(*optionalFoo) {}
    Bar(Foo& f) : foo(f) {}
    Bar(Bar const&) = delete; // Compiler generated one is incorrect
private:
    boost::optional<Foo> optionalFoo;
    Foo&                 foo;
};

But classes that optionally own their data get tricky, especially when it comes to copying/moving.

Nevin
  • 4,595
  • 18
  • 24