Почему по умолчанию принимается конструктор перемещения noexcept?

Предположим следующий код C ++ 17:

#include <type_traits>
namespace dtl
{
   struct One
   {
      explicit One(int);
      ~One() = default;
      One(const One &) = delete;
      auto operator=(const One &) -> One & = delete;
      auto operator=(One &&) -> One & = delete;
      One(One &&); // Throwable, not default;
      int m_int;
   };
   struct Two 
   {
      explicit Two(int);
      ~Two() = default;
      Two(const Two &) = delete;
      auto operator=(const Two &) -> Two & = delete;
      auto operator=(Two &&) noexcept -> Two & = delete;
      Two(Two &&) noexcept = default;
      One m_one;
   };
   One::One(One &&) { throw 1; }

   static_assert(std::is_nothrow_move_constructible_v<Two>);
}

Код в обозревателе компилятора

Здесь мы ясно видим, что конструктор перемещения класса One не помечен как noexcept. Класс Two имеет конструктор перемещения по умолчанию, для которого явно запрашивается значение noexcept.

Если мы проверим этот код, он компилируется с магистралью GCC, магистралью Clang, MSVC 19.28 и не работает с MSVC19.24.

Я проверил следующие источники, которые, кажется, говорят мне, что конструктор перемещения Two необходимо удалить:

CWG, выпуск 1778, чтобы прочитать (N4296 [ dcl.fct.def.default] / p3):

Если функция, которая явно задана по умолчанию, объявляется со спецификацией исключения, которая несовместима (15.4) со спецификацией исключения в неявном объявлении, то

если функция явно задана по умолчанию при ее первом объявлении, она определяется как удаленная; в противном случае программа имеет неправильный формат.

Основываясь на этой информации, я могу только сделать вывод, что все 3 компилятора ошибаются, рассматривая Two как no_throw_move_constructible, и конструктор перемещения должен быть неявно удален. Поскольку для всех троих странно игнорировать этот стандарт, я задаюсь вопросом: действительно ли это ошибка компилятора или я что-то упускаю.


person JVApen    schedule 31.12.2020    source источник


Ответы (1)


Я считаю, что вы смотрите на устаревшую информацию. DR1778 заменен на P1286R2. . Если вы посмотрите на статус реализации, вы увидите, что gcc 10 и clang 9 реализуют это новое разрешение.

Действительно, если вы вернетесь к более старым версиям gcc в Godbolt, он скажет вам:

<source>: In function 'int main()':
<source>:35:25: error: use of deleted function 'dtl::Two::Two(dtl::Two&&)'
   35 |     auto b = std::move(a);
      |                         ^
<source>:23:7: note: 'dtl::Two::Two(dtl::Two&&) noexcept' is implicitly deleted because its exception-specification does not match the implicit exception-specification ''
   23 |       Two(Two &&) noexcept = default;
      |       ^~~
Compiler returned: 1

Вы можете найти обсуждение gcc здесь. Согласно этому списку, P1286R2 был принят в качестве DR, что означает, что он был применен задним числом. к предыдущим стандартам. Таким образом, новые компиляторы будут вести себя так, как вы заметили, независимо от выбранного стандарта C ++.

Однако во время выполнения этого не произойдет, как и ожидалось:

dtl::One::One(int) {};
dtl::Two::Two(int) : m_one(0) {};

int main() {
   auto a = dtl::Two{1};
   try {
      auto b = std::move(a);
   } catch (...) {
      // Even though an exception is thrown, it will not be caught here because
      // we broke our `noexcept` promise.
      std::cout << "caught" << std::endl;
   }
   return 0;
}
[:~/tmp] $ /usr/local/Cellar/llvm/11.0.0/bin/clang++ -std=c++17 mv.cpp  && ./a.out
libc++abi.dylib: terminating with uncaught exception of type int
Abort trap: 6
person mrks    schedule 31.12.2020
comment
Спасибо за ввод, так что правильно ли я могу сказать, что Two(Two &&) noexcept = default; ведет себя так, как если бы мы написали Two(Two &&) noexcept = default; и вызовет std::terminate, если возникнет исключение? - person JVApen; 31.12.2020
comment
Вы написали Two(Two &&) noexcept = default; дважды, но да, terminate будет вызван. Я соответствующим образом обновил свой ответ. - person mrks; 31.12.2020
comment
Опа, ошибка копирования-вставки, я имел ввиду noexcept {} iso noexcept = default; . Звучит хорошо, спасибо - person JVApen; 31.12.2020