3

Why the following program cannot be compiled:

#include <memory>
#include <vector>

class something_t {
public:
    constexpr something_t() = default;

    constexpr something_t(const something_t& other)
        : field_(other.field_) {
    }

    constexpr something_t(something_t&& other)
        : field_(other.field_) {
    }

    constexpr something_t& operator=(const something_t& other) {
        field_ = other.field_;
        return *this;
    }

    constexpr something_t& operator=(something_t&& other) {
        field_ = other.field_;
        return *this;
    }

private:
    unsigned int field_{ 0 };
};

struct data_t {
    something_t something;
    std::vector<std::unique_ptr<int>> move_only; // <-- this line
};

int main() {
    std::vector<data_t> result;
    data_t data;
    result.push_back(std::move(data));
    return 0;
}

Error is (within g++):

/usr/include/c++/9/bits/stl_uninitialized.h:127:72: error: static assertion failed: result type must be constructible from value type of input range
127 |       static_assert(is_constructible<_ValueType2, decltype(*__first)>::value,
    |                                                                        ^~~~~        

(nearly the same with clang and MSVC).

But any of this compiles:

1) adding noexcept to something_t(something_t&&) move constructor

2) replacing line market with "this line" with std::unique_ptr<int> move_only:

    struct data_t {
        something_t something;
        std::unique_ptr<int> move_only;
    };

3) totally removing line marked with "this line":

    struct data_t {
        something_t something;
    };

4) adding default not-noexcept constructor to data_t:

    data_t() = default;
    data_t(data_t&&) = default;

We know that the type need to be noexcept move constructable, so why do fixes 2 and 4 do that when they shouldn't?

vladon
  • 8,158
  • 2
  • 47
  • 91
  • When `std::vector` needs to reallocate storage, it has to copy or move elements to the new memory block. It can only use the move constructor if it's `noexcept` (in order to provide a strong exception safety guarantee), otherwise it has to copy. – Igor Tandetnik May 31 '20 at 00:47
  • @IgorTandetnik I know this, but how it affects move-constructor here in `data_t`? It's only about resizing of vector, not constructing. – vladon May 31 '20 at 00:48
  • 2
    `result.push_back(...)` requires `result` to resize, and copy or move `data_t` elements. But `data_t` is not copyable, only movable, and its move constructor is not `noexcept` – Igor Tandetnik May 31 '20 at 00:49
  • @NathanOliver that question does not answers why removing std::vector helps. See #2 – vladon May 31 '20 at 00:59
  • It also compiles if you remove the `something_t` member variable from the `data_t` `struct` – WBuck May 31 '20 at 01:02
  • If you remove `std::vector> move_only;` then `data_t` is copyable and will be copied in the vector. – NathanOliver May 31 '20 at 01:03
  • @NathanOliver but if I use `std::unique_ptr move only` it also compiles, but it's not copyable because of std::unique_ptr is not copyable. This is strange, why is it? – vladon May 31 '20 at 01:04
  • I must admit I don't understand why #2 and #4 change the outcome. (#1 works by making the vector use move constructor; #3 works by making `data_t` copyable) – Igor Tandetnik May 31 '20 at 01:04
  • @vladon `std::unique_ptr` has a `noexcept` move constructor, so by default so does `data_t` – NathanOliver May 31 '20 at 01:05
  • vector requires either a copyable type, or a moveable type whose move constructor is noexcept. – NathanOliver May 31 '20 at 01:06
  • @NathanOliver But why? `something_t` in `data_t` has not a noexcept move constructor still. – vladon May 31 '20 at 01:06
  • @NathanOliver `std::vector` also has a `noexcept` move contructor. But the move constructor of `something_t` is not `noexcept` – Igor Tandetnik May 31 '20 at 01:06
  • @vladon Because `something_t` is copyable. – NathanOliver May 31 '20 at 01:07
  • @NathanOliver Are you saying that the implicitly-defined move constructor of `data_t` moves `move_only` member but copies `something` member? In any case, the copy constructor of `something_t` is not `noexcept` either. – Igor Tandetnik May 31 '20 at 01:08
  • @NathanOliver 1) `something_t` is not-`noexcept`-move-constructible and copyable + `std::vector>` is `noexcept`-move-constructible; 2) `something_t` is same, + `std::unique_ptr` is also `noexcept`-move constructible. What's the difference then? – vladon May 31 '20 at 01:08
  • @IgorTandetnik and the OP, Alright, there too much going on here so lets break it down. With the code as is, it doesn't work because `something_t` is not noexcept move constructable and `move_only` is only moveable so result` can only try to move but it cant because of the lack of noexcept. When you do fix #1, you make it noexcept move constructable. With fix #2 I'm not sure what is going on. With fix 3 `data_t` is a copyable type, so that is fine. fix number 4 also has be baffeled and I think it is related to fix #2. – NathanOliver May 31 '20 at 01:28
  • Maybe we edit the Q to say we know that the type need to be noexcept move constructable, so why do fixes 2 and 4 do that when they shouldn't? – NathanOliver May 31 '20 at 01:28

0 Answers0