std :: packaged_task должен был удалить копию c 'с параметром const - PullRequest
3 голосов
/ 21 января 2020

Ссылка https://cplusplus.github.io/LWG/issue2067 обеспечивает следующее обсуждение:

Шаблон класса packaged_task - это тип только для перемещения со следующей формой операций удаленного копирования: packaged_task(packaged_task&) = delete;
packaged_task& operator=(packaged_task&) = delete;
Обратите внимание, что типы аргументов неконстантны. Для меня это не опечатка, похоже, эта форма существует с самой первой статьи о предложении на N2276. Использование любой из форм конструктора копирования не имело большого значения перед введением по умолчанию специальных функций-членов, но теперь имеет заметное отличие. На это обратил мое внимание вопрос о немецкой группе новостей C ++, где был поднят вопрос, почему следующий код не компилируется в недавнем g cc:

#include <utility>
#include <future>
#include <iostream>
#include <thread>

int main() {
  std::packaged_task<void()> someTask([]{ std::cout << std::this_thread::get_id() << std::endl; });
  std::thread someThread(std::move(someTask)); // Error here
// Remainder omitted
}

Оказалось, что ошибка была вызвана созданием некоторого возвращаемого типа std :: bind, который использовал конструктор копирования по умолчанию, что приводит к конфликту объявления const с [class.copy] p8.

Некоторые аспекты Эта проблема, возможно, связана с основным языком, но я считаю, что это больше, чем просто услуга для программистов, если библиотека объявляет обычную форму операций копирования (т. е. с постоянным первым типом параметра) как удаленную для packaged_task, чтобы предотвратить подобные проблемы. .

Может ли кто-нибудь объяснить смысл отмеченного утверждения? Я не понимаю, как отсутствующий константный квалификатор влияет на процесс компиляции и как это поведение объясняется в стандарте. Какой смысл добавлять const к параметру конструктора удаленных копий?

1 Ответ

1 голос
/ 21 января 2020

Вот пример игрушка :

struct problem {
  problem()=default;
  problem(problem&&)=default;
  problem(problem&)=delete;
};

template<class T>
struct bob {
  T t;
  bob()=default;
  bob(bob&&)=default;
  bob(bob const&)=default;
};

int main() {
  problem p;
  problem p2 = std::move(p);
  bob<problem> b;
  bob<problem> b2 = std::move(b);
}

bob<problem> не удается скомпилировать, потому что bob(bob const&)=default выдает ошибку при взаимодействии с problem(problem&)=delete.

* 1010 Возможно, стандартное «следует» выдает ошибку, когда определяет, что он не может реализовать bob(bob const&), и обрабатывает =default как =delete (как если бы у нас было problem(problem const&)=delete), но стандартная формулировка - это не ' не будет безупречным в этом угловом корпусе углового корпуса. И этот угол углового случая будет достаточно странным и причудливым, поэтому я не уверен, что общее правило, которое заставляет его переводить =default в =delete, будет правильным!

Исправление, если мы problem(problem const&)=delete (ну, к packaged_task) будет намного чище, чем все, что мы делаем для =default правил ctor.

Теперь стандартное погружение:

Во-первых, очевидно, что у неявно объявленного конструктора копирования bob<problem> выше будет подпись bob(bob&) в [class.ctor] . Я даже не буду цитировать стандарт для этого, потому что ленивый.

Мы go и явно по умолчанию bob(bob const&) копируем ctor, который отличается в сигнатуре от того, который был бы неявно объявлен.

Существуют правила, касающиеся явно дефолтных функций, и их конфликт с сигнатурами приведен в 11.4.2.

В явно дефолтных функциях [dcl.fct.def.default] 11.4.2 / 2

2 Тип T1 явно дефолтной функции F может отличаться от типа T2, который был бы, если бы он был неявно объявлен, следующим образом:

- ( 2.1) T1 и T2 могут иметь разные ref-квалификаторы; и

- (2.2), если T2 имеет параметр типа const C&, соответствующий параметр T1 может иметь тип C&.

Если T1 отличается из T2 любым другим способом, затем:

- (2.3), если F является оператором присваивания, а тип возвращаемого значения T1 отличается от типа возвращаемого значения T2 или T1 тип параметра не является ссылкой, программа некорректна;

- (2.4) в противном случае, если F явно указано по умолчанию в первом объявлении, оно определяется как удаленное;

- (2.5) в противном случае программа некорректно сформирована.

По умолчанию установлено значение T1, которое содержит const&, а не &, поэтому (2.2) не применить.

Мое чтение действительно понято (2.4); тип bob(bob const&) отличается от неявно объявленного bob(bob&) недопустимым образом; но первое объявление default ed, поэтому оно должно быть delete d.

Я смотрю на черновую версию n4713 ; возможно, в более старой версии этого пункта не было.

...