9

The following structure fails to compile under C++11 due to the fact that I have declared the move assignment operator as noexcept:

struct foo
{
  std::vector<int> data;
  foo& operator=(foo&&) noexcept = default;
};

The default move assignment operator generated by the compiler is noexcept(false) due to the fact that std::vector<int>'s move assignment is also noexcept(false). This in turn is due to the fact that the default allocator has std::allocator_traits<T>:: propagate_on_container_move_assignment set to std::false_type. See also this question.

I believe this has been fixed in C++14 (see library defect 2103).

My question is, is there a way for me to force noexcept upon the default move assignment assignment operator without having to define it myself?

If this is not possible, is there a way I can trick the std::vector<int> into being noexcept move assignable so that noexcept(true) is passed through to my struct?

Community
  • 1
  • 1
marack
  • 2,024
  • 22
  • 31
  • 2
    Is it okay if `data` is a `std::vector` where `A` is not the default `std::allocator`? – aschepler Sep 11 '13 at 00:49
  • 1
    I'm looking to keep code complexity at a minimum. I'd expect that using a custom allocator is harder for other coders to understand than a simple non-default move assignment operator. – marack Sep 11 '13 at 01:22
  • 1
    That said, if the `A` in `std::vector` is a simple inline wrapper for the default allocator it might work... – marack Sep 11 '13 at 01:30
  • What is suppose to happen if the move operation on your data throws? – Fozi Dec 03 '13 at 21:36

2 Answers2

7

I believe this has been fixed in C++14 (see library defect 2103).

As a DR that fix should be considered a correction to C++11 and so some C++11 implementations will have already fixed it.

My question is, is there a way for me to force noexcept upon the default move assignment assignment operator without having to define it myself?

For the defaulted move assignment operator to be noexcept you need to make its sub-objects have noexcept move assignment operators.

The most obvious portable way I can think of is to use a wrapper around std::vector which forces the move to be noexcept

template<typename T, typename A = std::allocator<T>>
  struct Vector : std::vector<T, A>
  {
    using vector::vector;

    Vector& operator=(Vector&& v) noexcept
    {
      static_cast<std::vector<T,A>&>(*this) = std::move(v);
      return *this;
    }
    Vector& operator=(const Vector&) = default;
  };

Another similar option is to define your own allocator type with the DR 2013 fix and use that:

template<typename T>
  struct Allocator : std::allocator<T>
  {
    Allocator() = default;
    template<typename U> Allocator(const Allocator<U>&) { }
    using propagate_on_container_move_assignment  = true_type;
    template<typename U> struct rebind { using other = Allocator<U>; };
  };

template<typename T>
  using Vector = std::vector<T, Allocator<T>>;

Another option is to use a standard library implementation such as GCC's which implements the resolution to DR 2013 and also makes std::vector's move assignment operator noexcept for other allocator types when it is known that all allocator instances compare equal.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
1

I do not think you can force anything, but you may wrap it:

#include <iostream>
#include <vector>

template <typename T>
struct Wrap
{
    public:
    Wrap() noexcept
    {
        new (m_value) T;
    }

    Wrap(const Wrap& other) noexcept
    {
        new (m_value) T(std::move(other.value()));
    }

    Wrap(Wrap&& other) noexcept
    {
        std::swap(value(), other.value());
    }


    Wrap(const T& other) noexcept
    {
        new (m_value) T(std::move(other));
    }

    Wrap(T&& other) noexcept
    {
        new (m_value) T(std::move(other));
    }

    ~Wrap() noexcept
    {
        value().~T();
    }

    Wrap& operator = (const Wrap& other) noexcept
    {
        value() = other.value();
        return *this;
    }

    Wrap& operator = (Wrap&& other) noexcept
    {
        value() = std::move(other.value());
        return *this;
    }

    Wrap& operator = (const T& other) noexcept
    {
        value() = other;
        return *this;
    }

    Wrap& operator = (T&& other) noexcept
    {
        value() = std::move(other);
        return *this;
    }

    T& value() noexcept { return *reinterpret_cast<T*>(m_value); }
    const T& value() const noexcept { return *reinterpret_cast<const T*>(m_value); }
    operator T& () noexcept { return value(); }
    operator const T& () const noexcept { return value(); }

    private:
    typename std::aligned_storage <sizeof(T), std::alignment_of<T>::value>::type m_value[1];
};


struct Foo
{
    public:
    Foo& operator = (Foo&&)  noexcept = default;

    std::vector<int>& data() noexcept { return m_data; }
    const std::vector<int>& data() const noexcept { return m_data; }

    private:
    Wrap<std::vector<int>> m_data;
};

int main() {
    Foo foo;
    foo.data().push_back(1);
    Foo boo;
    boo = std::move(foo);
    // 01
    std::cout << foo.data().size() << boo.data().size() << std::endl;
    return 0;
}

(Thanks Jonathan Wakely)

  • 2
    That type is not copyable, you need copy ctor and assignment op. Also, you can use `std::aligned_storage::type` instead of a char array. – Jonathan Wakely Dec 03 '13 at 12:51