15

This sounds like a basic question, but I didn't find any comprehensive answer, so here it is. Consider this code snippet:

struct A {
    const std::string& s;
    A(const std::string& s) : s(s) {}
};

int main() {
    A a("abc");
    std::cout << a.s << std::endl;
    return 0;
}

Demo.

As long as I understand, this is UB. String literal "abc" binds to const std::string& in constructor, creating a temporary string object. It is also bound to reference a.s, and it is destroyed once a is constructed. That is, const reference cannot chain lifetime prolongation. Dangling reference, boom. In this particular case I see no output at all on ideone.com, but anything could happen (remember velociraptors).

Ok, this one is clear. But what if this is actually our very intent: we want to store a const reference to an object? To an existing one, not to temporary? This sounds like a very natural task, yet I came up with only one (almost) natural solution to it. Accepting constructor's argument by std::reference_wrapper instead of by reference:

    A(std::reference_wrapper<const std::string> r) : s(r) {}

Since std::reference_wrapper has deleted constructors from temporaries:

reference_wrapper( T&& x ) = delete;

this works just like expected. However, this is not quite elegant. Another approach I can think of is to accept forwarding reference T&& and to reject everything except const l-value strings with std::enable_if. This is even less elegant, I think.

Any other approaches?

UPD Another question: is this a legitimate usage of std::reference_wrapper, or may it be considered too specific?

Mikhail
  • 20,685
  • 7
  • 70
  • 146
  • 1
    Any reason why `A` cannot just have a regular `std::string`? `A::s` is likely to dangle even if it receives an lvalue and the reference breaks the assignment operator. Does this have a use case or is it purely academic? – nwp Mar 03 '16 at 11:12
  • 3
    `A(const std::string &&) = delete;` might do what you want. I still don't think it is a good idea. – nwp Mar 03 '16 at 11:15
  • @nwp This might be useful in utility classes, where we control lifetime of its instances and may guarantee they will not outlive passed object. I used `std::string` to denote an expensive to copy object. An alternative, safer, solution would be to store `shared_ptr` on this object. However, this might be an overkill in simple scenarios. – Mikhail Mar 03 '16 at 12:15
  • @nwp a use case that stands out to me is a no-copy string view. – Joel Cornett Mar 03 '16 at 15:25
  • I think your solution is pretty nice actually. If you want to make it read a bit better, you might use an alias template for std::reference_wrapper. – Vaughn Cato Mar 03 '16 at 17:50

2 Answers2

7

I'd say the natural solution would be to do what reference_wrapper does: prevent construction from temporaries:

struct A {
    const std::string& s;
    A(const std::string& s) : s(s) {}
    A(std::string&&) = delete;
};

You should also bear in mind that having a data member of reference type makes the class non-assignable (not even move assignment is possible) by default, and it's generally difficult to implement an assignment operator. You should consider storing a pointer instead of a reference:

struct A {
    const std::string* s;
    A(const std::string& s) : s(&s) {}
    A(std::string&&) = delete;
};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 2
    Good answer. The one downside is that if we have multiple such arguments, it would be quite difficult to `delete` all unwanted flavors. – Mikhail Mar 03 '16 at 12:08
  • 1
    I don't like pointer approach - it has no syntactic information that it cannot be nullptr. – Mikhail Mar 03 '16 at 12:17
  • @Mikhail I consider an imperfect, but usable object superioir to a pure, but unusable one ;-) Still, you could use something like `not_null` from the [Core Guideline library](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md) – Angew is no longer proud of SO Mar 03 '16 at 12:36
  • Yes, also thought about `not_null` here. – Mikhail Mar 03 '16 at 12:37
1

There is a very simple class lvalue_ref provided in the lightweight and convenient "explicit" library by Andrzej Krzemieński, which solves exactly this problem with a clear intention:

struct Processor
{
  Big const& _big;
  explicit Processor(lvalue_ref<const Big> b) : _big(b) {}
};

const Big b {};
Processor p {b}; // ok
Processor q {Big{}}; // error (temporary)
Mikhail
  • 20,685
  • 7
  • 70
  • 146