#опытным
В тему noexcept. В этом посте мы рассказали о том, что noexcept конструктор позволяет разрешить перемещения элементов при реаллокациях std::vector. Однако даже если ваш мув-конструктор определен, но не помечен noexcept, и нет копирующего конструктора, то вектору все равно разрешается перемещать элементы. За это необычное поведение ответственна функция std::move_if_noexcept. Сегодня посмотрим, за счет чего такое поведение достигается.
Вот реализация этой функции в gcc:
template<typename _Tp>
struct __move_if_noexcept_cond
: public _and<_not<is_nothrow_move_constructible<_Tp>>,
is_copy_constructible<_Tp>>::type { };
template<typename _Tp>
[[nodiscard,gnu::always_inline]]
constexpr
__conditional_t<__move_if_noexcept_cond<_Tp>::value, const _Tp&, _Tp&&>
move_if_noexcept(_Tp& __x) noexcept
{ return std::move(__x); }
Две части: условия мува и сам мув.
Все работает буквально на одних type trait'ах. Если условие move_if_noexcept_cond правдиво, то результат мува кастуется к константной левой ссылке, чтобы в итоге мува в итоге не произошло. Если ложное, то результат кастится к правой ссылке, что потенциально и разрешает мув.
Условие move_if_noexcept_cond истинно только в одном случае: когда мув-конструктор бросающий и есть копирующий конструктор. Получается, что это единственная ситуация, когда мува не произойдет. Во всех остальных случаях значение скастуется к правой ссылке.
Не спрашивайте меня, почему условие мува как будто бы перевернутое. Странное решение. Если кто знает, поясните в комментах.
Кстати тут есть интересный момент. Если класс удовлетворяет трейту *move_constructible, то это не значит, что у него есть мув конструктор! *move_constructible всего лишь значит, что объект можно скрафтить из правой ссылки. А правые ссылки могут приводиться к константным левым ссылкам. И даже если ваш класс не будет иметь мув-конструктора, но его копирующий конструктор принимает константную левую ссылку, то этот класс будет удовлетворять условию is_move_constructable:
struct NoMove1
{
// prevents implicit declaration of default move constructor;
// however, the class is still move-constructible because its
// copy constructor can bind to an rvalue argument
NoMove1(const NoMove1&) {}
};
static_assert(std::is_move_constructible_v<NoMove1>); // Here
static_assert(!std::is_trivially_move_constructible_v<NoMove1>);
static_assert(!std::is_nothrow_move_constructible_v<NoMove1>);
struct NoMove2
{
// Not move-constructible since the lvalue reference
// can't bind to the rvalue argument
NoMove2(NoMove2&) {}
};
static_assert(!std::is_move_constructible_v<NoMove2>); // And here
static_assert(!std::is_trivially_move_constructible_v<NoMove2>);
static_assert(!std::is_nothrow_move_constructible_v<NoMove2>);
То есть использование трейта std::is_nothrow_move_constructible на классе, не имеющем мув-конструктора, абсолютно легально.
В общем, просто хотел рассказать про эти два интересных момента. Это может быть важно при проектировке своих структур данных.
Live legally. Stay cool.
#template #cppcore #STL