Can I force a default special member function to be noexcept?

3.4k views Asked by At

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?

2

There are 2 answers

0
Jonathan Wakely On BEST ANSWER

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.

1
AudioBubble On

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)