3

Let's say I have something like this:

class A
{
public:
    A(int x, int y)
    {
        std::cout << "Constructed from parameters" << std::endl;
    }

    // Non-copyable, non-movable
    A(A const&) = delete;
    A(A&&) = delete;
    A& operator=(A const&) = delete;
    A& operator=(A&&) = delete;
};
    
class B
{
public:
    B() = default;

private:
    A a{1,2};
};
    
int main()
{
    B b;
    return 0;
}

This works fine, initializes a with a default value by calling A's constructor, and prints Constructed from parameters.


Then, let's say I want to initialize a differently, so I add a constructor that takes an initializer list and change my code:

class A
{
public:
    A(int x, int y)
    {
        std::cout << "Constructed from parameters" << std::endl;
    }
        
    A(std::initializer_list<int> someList)
    {
        std::cout << "Constructed from an initializer list" << std::endl;
    }

    // Non-copyable, non-movable
    A(A const&) = delete;
    A(A&&) = delete;
    A& operator=(A const&) = delete;
    A& operator=(A&&) = delete;
};
    
class B
{
public:
    B() = default;

private:
    A a{1,2,3};
};
    
int main()
{
    B b;
    return 0;
}

Everything still works as I want, the code initializes a with a default value by calling A's second constructor and prints Constructed from an initializer list.


Now, let's say I want to keep A as is but go back to setting a's default value through the first constructor. If A was movable, I could use A a = A(1,2);, but that isn't the case here, so how do I go about this? Is it plain impossible to set a default value with that constructor?

Edit: I'm looking for a solution that would work with C++14, but if there is a better solution in C++17 or C++20 that's also something I want to know.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Eternal
  • 2,648
  • 2
  • 15
  • 21
  • Please add a language standard tag. An answer would be drastically different for [tag:c++11] and [tag:c++17]. – Evg Oct 22 '20 at 11:42
  • I didn't put one because I'm interested in how whether the answer changed too, but you're right, I should specify that somewhere – Eternal Oct 22 '20 at 11:54
  • Adding several language tags is fine. Just be explicit. – Evg Oct 22 '20 at 11:56
  • Unfortunately the website doesn't let me add c++14/17/20 as tags because 5 tags seems to be the maximum – Eternal Oct 22 '20 at 12:04

2 Answers2

4

If A was movable, I could use A a = A(1,2);, but that isn't the case here

Well, for C++17 and later, you can use A a = A(1,2); here, so long as you have that as the declaration/initialisation! The following works (in your second code snippet):

class B {
public:
    B() = default;
private:
    A a = A( 1, 2 ); // No assignment here - just an initialization.
};

and "Constructed from parameters" is called. This is because there is no actual assignment operation here, just an initialization. However, the following would fail, for the reason you have stated:

class B {
public:
    B() { a = A( 1, 2 ); } // error C2280: 'A &A::operator =(A &&)': attempting to reference a deleted function
private:
    A a{ 1,2,3 };
};

EDIT: Pre-C++17, the following is a workaround, using the 'old-fashioned' round parentheses, rather than curly braces, in an initializer list in the default constructor for B (tested with C++14 in clang-cl and MSVC):

class B {
public:
    B() : a(1,2) {} // Using a{1,2} calls the "initializer list" constructor, however!
private:
    A a;
};
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • That's definitely part of what I wanted to know. It does indeed seem that c++17 specified that no move or copy must happen here in such a case. What about before c++17 though? Is there no way to do it and that's why it was fixed by c++17? – Eternal Oct 22 '20 at 12:01
  • @AdrianMole What I'm looking for is a default value for `a`, so your c++14 solution is a bit besides the point, since it boils down to explictly initializing `a` in each constructor – Eternal Oct 22 '20 at 12:52
  • @Eternal OK, I get your point. However, in terms of generated code, then even a "default value" has to be actually initialized when an object of the class is created. But I would also be very interested to see if anyone can come up with a formal 'default' initializer that forces use of the "from parameters" constructor. – Adrian Mole Oct 22 '20 at 13:49
  • @AdrianMole Indeed. If nobody else can come up with a solution, maybe the correct answer is that default values were impossible in this case before c++17 (in which case if you edit it to say that I'll mark it as accepted) – Eternal Oct 22 '20 at 14:11
  • @Eternal I'm not (yet) prepared to say that your task was *impossible* pre-C++17! The issue is in disambiguating the two constructors, which can be a very tricky issue. I've tried various combinations of `explicit` ... but to no avail. – Adrian Mole Oct 22 '20 at 14:16
0

I think this is not possible in C++14 without some kind of a "hack". One such hack is to employ an additional disambiguating parameter. For example:

class From_params {};

class A {
public:
    A(int, int, From_params = {})      // (1)
    {}
    
    A(std::initializer_list<int>)      // (2)
    {}

    ...
};

class B {
public:
    B() = default;

private:
    A a{1, 2, From_params{}};  // calls (1)
};
Evg
  • 25,259
  • 5
  • 41
  • 83
  • Is that static dispatch? I'll admit that this works, but only if A was actually written by me and I was free to modify it. Even though I wrote the question like this to make things clearer, in practice that's not the case (hence why I said I wanted to keep A as is). The reality is that classes that have both parameter and list constructors are common (std::vector for example), so I've gotten into the habit of using `A a = A( 1, 2 );` systematically... until I tried to initialize an unmovable object (std::atomic), which made me wonder what I could do if I had both constraints – Eternal Oct 23 '20 at 08:06
  • @Eternal, it is static. This is not a universal solution, just a hack. I don't know how to initialize a member using `()` syntax within in-line initialization. I guess it is impossible. – Evg Oct 23 '20 at 08:14