How do I set a default value for a class member with something other than its initializer list constructor

846 views Asked by At

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.

2

There are 2 answers

2
Evg On

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)
};
5
Adrian Mole On

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;
};