Delegate constructor and default argument depending on other arguments
Asked Answered
I

2

7

I have a constructor for B with some default argument depending on other arguments:

struct A
{
    int f();
    A(const A&) = delete;
    A(A&& );
    // ....
};

struct B
{
    B(A a, int n=a.f()) {//...}
    // ...
};

This clearly does not work in that way, so I want use a delegate constructor:

struct B
{
      B(A a, int n) {//...}
      B(A a): B(a, a.f()) {}
};

This, however, also does not work because the copy constructor of A is deleted. So I need something like

struct B
{
      B(A a, int n) {//...}
      B(A a): B(std::move(a), a.f()) {}
};

As far as I know, however, there is no guarantee that a.f() is evaluated before std::move, so the result is undefined. Is there a possiblity to get the value of a.f() before std::move or should I better write two seperate constructors?

Iodate answered 12/1, 2019 at 9:22 Comment(2)
For an easy life can't you just make the copy constructor in A protected?Murrell
I could. This is only a simplified version to show the essence of the problem; the real situation is actually more compilcated.Iodate
H
4

Why don't you do something simpler - i.e. overload your constructor?

struct B
{
    B(A a) {
        int n = a.f();
        ...
    }
    B(A a, int n) {
        ...
    }
};

If you don't like repetition of your code in ..., you can always have just a single call to a private member function that does the rest of the construction.

Hatter answered 12/1, 2019 at 9:48 Comment(3)
Yes, this is also the version that I am currently using. It works fine, but I would prefer a delegate constructor because it makes the code dependencies more clear. (That is the reason why delegate constructors were introduced).Iodate
@HelmutZeisel but also, you want a specific order of evaluation. That type of reasoning is handled by imperative trait of C++, not functional. So, I think, having a sequence of statements, rather than nifty function calls is the way to go in your case.Hatter
Yes. I will stay with the solution without delegate constructor. Which solution is clearer is mainly a question of taste. In addition, as this example also shows, the delegate constructor is less efficient since it does not allow copy elision (which is, however, not important in that particular case).Iodate
A
0

The are more possible solutions for this.

  1. The most simple approach is to make a a pointer:

    struct B
    {
         B(A* a, int n) {...}
         B(A* a): B(a, a->f()) {}
    };
    
  2. A more complex approach is to try to make a a reference:

    struct B
    {
          B(A& a, int n) {...}
          B(A& a): B(a, a.f()) {}
    };
    

I would not suggest this solution. The pointer is a cleaner approach.

Edit:

  1. Via std::move from the utility libary

    struct B
    {
          A&& a:
          int n:
    
          B(A&& a, int n): a(std::move(a)), n(n) {...}
          B(A&& a): B(std::move(a), a.f()) {...}
    };
    
Accumulation answered 12/1, 2019 at 9:34 Comment(9)
In cites or lists you just have to add four more spaces to indent the code.Evacuee
Yes, that work's, but there is a difference in the semantics. I want that B takes the ownership of a, but in your case the caller keeps the ownership.Iodate
the thing your are searching for mite be a move constructor. I will update the answer if i have tested it.Accumulation
I added a 3. option to the other two, hope this works for you.Accumulation
@HelmutZeisel You can take the ownership in the member initialization list (if you want a member of B to own a) or in the constructor body, for example, B(A&& a, int n) {auto aa = std::move(a);}.Eleemosynary
I am concerned about your (3) option. There is no guarantee that a.f() is invoked before std::move(a). The very same problem was already noticed by OP in his question. If you think this is not a problem, please defend your answer, because currently - as it stands - I think it is a potential error.Hatter
@Hatter It is OK because the poster uses only references. There is no move.Eleemosynary
@Hatter I understand your concern and try to force the error. For some reason i failed and even this worked:B(A&& aa): B(std::move(aa), aa.f()) { aa.val = 67890; }Accumulation
@Eleemosynary In my opinion when you need to use a pointer, you try to use dynamic variables, which are not destroyed if you go out of a function. This can prevent some really messy errors.Accumulation

© 2022 - 2024 — McMap. All rights reserved.