2

I would like to have a class B which has 3 member objects of another class A. Constructors of both A and B are constexpr. A - which is contained inside B - is non-copyable and non-movable. This code builds correctly:

class A
{
public:
    constexpr explicit A(int a) {}
    A(const A&) = delete;
    A(A&&) = delete;
};

class B
{
public:
    constexpr B() :
            a0{0},
            a1{1},
            a2{2}
    {}
private:
    A a0;
    A a1;
    A a2;
};

int main()
{
    B b;
}

However I would really like to have the 3 objects of type A as an array. If I try the simple approach like this:

class A
{
public:
    constexpr explicit A(int a) {}
    A(const A&) = delete;
    A(A&&) = delete;
};

class B
{
public:
    constexpr B() :
            a{A{1}, A{2}, A{3}}
    {}
private:
    A a[3];
};

int main()
{
    B b;
}

fails to build with:

$ g++ a.cpp 
a.cpp: In constructor ‘constexpr B::B()’:
a.cpp:21:22: error: use of deleted function ‘A::A(A&&)’
    a{A{1}, A{2}, A{3}}
                      ^
a.cpp:13:2: note: declared here
  A(A&&) = delete;
  ^
a.cpp:21:22: error: use of deleted function ‘A::A(A&&)’
    a{A{1}, A{2}, A{3}}
                      ^
a.cpp:13:2: note: declared here
  A(A&&) = delete;
  ^
a.cpp:21:22: error: use of deleted function ‘A::A(A&&)’
    a{A{1}, A{2}, A{3}}
                      ^
a.cpp:13:2: note: declared here
  A(A&&) = delete;
  ^
a.cpp:28:2: error: member ‘B::a’ must be initialized by mem-initializer in ‘constexpr’ constructor
  }
  ^
a.cpp:32:7: note: declared here
  A a[3];
       ^

Is it possible to solve without making A movable?

EDIT:

As suggested by @rustyx I've changed the code a bit and it works fine for C++11 and C++17 (with explicit). However - as usually - the real code is a bit more complex. Let's say that A is really non-movable and non-copyable, say that it has a destructor.

class A
{
public:
    constexpr explicit A(int a) {}
    ~A() {}
    A(const A&) = delete;
    A(A&&) = delete;
};

class B
{
public:
    constexpr B() :
            a{A{1}, A{2}, A{3}}
    {}
private:
    A a[3];
};

int main()
{
    B b;
}

This fails even with C++17:

g++ a.cpp -std=c++17
a.cpp: In constructor ‘constexpr B::B()’:
a.cpp:14:22: error: use of deleted function ‘A::A(A&&)’
    a{A{1}, A{2}, A{3}}
                      ^
a.cpp:7:2: note: declared here
  A(A&&) = delete;
  ^
a.cpp:14:22: error: non-constant array initialization
    a{A{1}, A{2}, A{3}}
                      ^
a.cpp:15:3: error: use of deleted function ‘A::A(A&&)’
  {}
   ^
a.cpp:7:2: note: declared here
  A(A&&) = delete;
  ^

It fails also if A's constructor is not explicit. If I remove the destructor then it works, but what if the destructor has to be there? Is there a solution to this particular array-initialization problem or am I out of luck here?

Freddie Chopin
  • 8,440
  • 2
  • 28
  • 58

1 Answers1

4

Strictly speaking, initializing an instance of A from A{1} is a copy- (or move) initialization. Most compilers elide the copy/move and don't even bother calling the copy/move constructor, but only starting from C++17 is presence of a copy/move constructor actually not required.

As a workaround you can remove explicit from As constructor and construct As in-place:

class A
{
public:
    constexpr A(int a) {}
    A(const A&) = delete;
    A(A&&) = delete;
};

class B
{
public:
    constexpr B() :
            a{{1}, {2}, {3}}
    {}
private:
    A a[3];
};

int main()
{
    B b;
}

=== EDIT === (in response to question edit)

Let's say that A is really non-movable and non-copyable, say that it has a destructor.

The only possible workaround I can think of is the nuclear option a.k.a. placement-new:

#include <memory>
#include <type_traits>
class A
{
public:
    constexpr A(int a) {}
    A(const A&) = delete;
    A(A&&) = delete;
    ~A() {}
};

class B
{
public:
    B() {
        new (std::addressof(a[0])) A(1);
        new (std::addressof(a[1])) A(2);
        new (std::addressof(a[2])) A(3);
    }
    A& getA(size_t offset) { return reinterpret_cast<A*>(a)[offset]; }
private:
    std::aligned_storage<sizeof(A), alignof(A)>::type a[3];
};

int main()
{
    B b;
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Indeed, this works in the simple case, but as usually the real code is a bit more complex (; Could you please take a look at my edited question? – Freddie Chopin Apr 29 '19 at 09:08