1

I want to make a wrapper for a constructor of a class B.

Since some part of the class B is replaceable, I group each implementation of those parts into several class A.

I use perfect forwarding to provide a default implementation of A for constructing a B.

The problem is the wrapper doesn't work. How to fix this?

Thanks.

The following is also at https://godbolt.org/g/AWwtbf

template<typename T>
class A {
public:
    A() { _x = 2; }
    int _x;
};

template<typename T, typename C=A<T> >
class B {
public:
    explicit B(T x, C&& a = A<T>())
            : _x(x), _a(a) {
    }
    T _x;
    A<T>& _a;
};

template<typename T, typename C=A<T> >
B<T, A<T>> make_b(T x, C&& c = A<T>()) {
    return B<int, A<int>>(x, c);
};

int main() {
    B<int, A<int>> b1(1);  // this works.
    auto b2 = make_b(1);  // this doesn't
}

Error:

error: cannot bind rvalue reference of type 'A<int>&&' to lvalue of type 'A<int>'

     return B<int, A<int>>(x, c);
R zu
  • 2,034
  • 12
  • 30
  • 3
    Read [this](http://thbecker.net/articles/rvalue_references/section_01.html). You seem to have some fundamental misunderstandings – Passer By Apr 13 '18 at 15:58
  • After reading pages 1 and 2, I think that `A()` is a rvalue. It represents calling the constructor of `A` instead of the actual result, which is an instance of `A`. If `A` has a move constructor that takes in this rvalue and create the actual instance of `A`, and change type of `B::_a` to `A` instead of `A&`, it would be fine? – R zu Apr 13 '18 at 16:35
  • That sounds too complicated. B and A would be created at the same time. I will just ask B to have an A as an attribute. And maybe also make `B::_a` public. That way, creating a B would also create a A, but I have the flexibility to choose what kind of A is created. – R zu Apr 13 '18 at 16:41
  • You should continue reading, it will be worth it if you are planning to do anything serious with C++. Every expression has a value category, it is either lvalue or rvalue. `A()` is indeed a rvalue (its value category) and is also an `A` (its type). This is the _basis_ of perfect forwarding, but is only half the story. – Passer By Apr 13 '18 at 19:02

1 Answers1

2

How to perfect-forward stuff?

  1. Make the type of the argument a template parameter.
  2. Accept the argument by rvalue-reference (&&).
  3. std::forward the argument.

You got nr. 1 and 2 but forgot nr. 3:

template<typename T, typename C=A<T> >
B<T, A<T>> make_b(T x, C&& c = A<T>()) {
    return B<T, A<int>>(x, std::forward<C>(c));
};

How to avoid a dangling reference?

This code creates a dangling reference, i.e. a reference to an object that's already gone:

    explicit B(T x, C&& a = A<T>())
            : _x(x), _a(a) {
    }
    T _x;
    A<T>& _a;

Consider what happens here: a temporary A is created, the rvalue reference a is being used in an lvalue expression (a) so it turns into an lvalue reference and A& _a subsequently happily binds to it. By the time the full-expression is finished (at the first ;) the temporary A instance is destroyed and A& _a refers to a non-existent object. If _a is used after that point, the behavior is undefined.

Avoid references as data members until you understand value categories, object lifetime and reference collapsing rules.

Just store A by value:

    explicit B(T x, C&& a = A<T>())
            : _x(x), _a(std::move(a)) {
    }
    T _x;
    A<T> _a;

Or instantiate A outside B and pass it as an lvalue reference:

    explicit B(T x, C& a) : _x(x), _a(a) {}
    C& _a;
rustyx
  • 80,671
  • 25
  • 200
  • 267