Черта типа для подвижных типов?

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

Любая помощь приветствуется.


person Kranar    schedule 14.08.2011    source источник
comment
Не могли бы вы немного пояснить свой вопрос? Даже если для типа не определен явный конструктор перемещения, обычно его можно сконструировать как T t2{ std:: move(t1) };. Это выражение вернется к конструктору копирования, если он присутствует. Итак, что именно вы спрашиваете? Вы хотите конкретно проверить, был ли определен явный конструктор перемещения (довольно узкий тест)? Или, в более широком смысле, вы хотите проверить, может ли он быть построен из r-значения (легко проверить, будет включать почти все типы, у которых есть конструктор копирования)?   -  person Aaron McDaid    schedule 09.01.2015
comment
Чтобы упростить мой вопрос: я думаю, я спрашиваю о ваших словах ... ведет себя так, если T имеет конструктор перемещения.... Возможно, вы имеете в виду, что ... ведет себя так, если тип T имеет явно определенный конструктор с сигнатурой T :: T(T&&)...   -  person Aaron McDaid    schedule 09.01.2015


Ответы (6)


Я чувствую необходимость указать на тонкое различие.

Хотя <type_traits> предоставляет std::is_move_constructible и std::is_move_assignable, они точно не определяют, есть ли у типа конструктор перемещения (соответственно, оператор присваивания перемещения) или нет. Например, std::is_move_constructible<int>::value равно true, и рассмотрим также следующий случай:

struct copy_only {
    copy_only(copy_only const&) {} // note: not defaulted on first declaration
};
static_assert( std::is_move_constructible<copy_only>::value
             , "This won't trip" );

Обратите внимание, что объявленный пользователем конструктор копирования подавляет неявное объявление конструктора перемещения: нет даже скрытого, сгенерированного компилятором copy_only(copy_only&&).

Признаки типов предназначены для облегчения универсального программирования и, таким образом, указываются в терминах выражений (из-за отсутствия понятий). std::is_move_constructible<T>::value задает вопрос: например. T t = T{}; действует? Он не спрашивает (при условии, что T здесь является типом класса), объявлен ли конструктор перемещения T(T&&) (или любой другой допустимой формы).

Я не знаю, что вы пытаетесь сделать, и у меня нет причин не верить, что std::is_move_constructible не подходит для ваших целей.

person Luc Danton    schedule 14.08.2011
comment
В настроении комментировать - is_move_assignable сообщает нам, будет ли работать t = std::move(u), а не перемещать или копировать значения. Для пустого объекта copy_only все равно особой разницы не будет. - person Bo Persson; 14.08.2011
comment
+1 N3142 (open-std.org/ jtc1/sc22/wg21/docs/papers/2010/n3142.html) был введен в конце процесса, просто чтобы избежать недоразумений, что Люк хорошо разъясняет этот ответ: has_move_constructor был переименован в is_move_contructible, так что название более точно отражало его истинную семантику. - person Howard Hinnant; 14.08.2011
comment
Привет, ребята, я опубликовал новый ответ на этот вопрос, показывающий, как мы можем вызвать ошибку неоднозначности, когда конструктор перемещения присутствует рядом с конструктором копирования. Это может быть встроено в полную черту really_has_a_move_constructor. Любая обратная связь приветствуется. Я надеюсь, что это полностью соответствует стандартам, но я действительно ничего не знаю о разрешении перегрузки! - person Aaron McDaid; 11.01.2015
comment
Я не знаю, что вы пытаетесь сделать, и у меня нет причин не верить, что std::is_move_constructible не подходит для ваших целей. Я могу объяснить, почему этого недостаточно, так как я запускал в ту же проблему. Если вы пытаетесь реализовать операцию отката, очень важно, вызываете ли вы конструктор перемещения или конструктор копирования. Почему? Поскольку откат копии не является операцией и может быть полностью пропущен (на самом деле его следует пропустить, чтобы избежать возникновения исключения), в то время как для отката движения требуется вернуться к исходному местоположению. . - person user541686; 15.12.2020

Он называется std::is_move_constructable. Есть еще std::is_move_assignable. Они оба находятся в заголовке C++0x <type_traits>.

person Nicol Bolas    schedule 14.08.2011

После небольшого обсуждения и полного согласия с тем, что это может быть совершенно бесполезным, и с предупреждением о том, что старые компиляторы могут сделать это неправильно, я, тем не менее, хотел бы вставить небольшой класс признаков, который я смонтировал, и я верю, что он даст вам true только тогда, когда класс имеет конструктор перемещения:

#include <type_traits>

template <typename T, bool P> struct is_movecopy_helper;

template <typename T>
struct is_movecopy_helper<T, false>
{
  typedef T type;
};

template <typename T>
struct is_movecopy_helper<T, true>
{
  template <typename U>
  struct Dummy : public U
  {
    Dummy(const Dummy&) = delete;
    Dummy(Dummy&&) = default;
  };
  typedef Dummy<T> type;
};

template <class T>
struct has_move_constructor
 : std::integral_constant<bool, std::is_class<T>::value &&
   std::is_move_constructible<typename is_movecopy_helper<T, std::is_class<T>::value>::type>::value> { };

Использование: has_move_constructor<T>::value

Обратите внимание, что черта компилятора std::is_move_constructible на самом деле не поставляется с GCC 4.6.1 и должна предоставляться отдельно, см. мой полный код< /а>.

person Kerrek SB    schedule 14.08.2011
comment
Я не могу заставить это работать сейчас. лязг-3.3. Прошло два года с тех пор, как вы опубликовали этот ответ, так что, я думаю, все сдвинулось с мертвой точки! Насколько я понимаю, для класса, конструируемого с помощью копирования, единственным способом, которым он может быть не-конструируемым для перемещения, является наличие у него конструктора private перемещения. В любом случае, я думаю, ваша идея заключалась в том, что default конструкторы перемещения могут быть созданы только тогда, когда все суперклассы уже имеют конструкторы перемещения? - person Aaron McDaid; 03.11.2013
comment
@AaronMcDaid: я думаю, что смысл этой черты в том, чтобы определить, есть ли у класса настоящий конструктор перемещения, который не просто возвращается к конструктору копирования. Все, что можно скопировать, по умолчанию автоматически перемещается. - person Kerrek SB; 03.11.2013
comment
«настоящий» конструктор перемещения, который не просто возвращается к конструктору копирования. Это то, на что я надеялся! Но мои эксперименты с вашим кодом не увенчались успехом. Я создаю класс с различными конструкторами, включая конструктор копирования, но не конструктор перемещения, а ваш трейт имеет value==1. У вас есть пример кода, который показывает, что он работает? Ссылка, которая у вас была на ideone, мертва. - person Aaron McDaid; 03.11.2013
comment
@AaronMcDaid: Хм, я думаю, что то, что я только что сказал, не так легко выполнимо. Я не знаю сейчас, может быть, код, который я разместил, является просто громоздкой версией is_class и is_move_constructible и на самом деле не говорит, был ли объявлен конструктор перемещения. Позор, но я не вижу очевидного способа добиться этого прямо сейчас. - person Kerrek SB; 03.11.2013
comment
Я только что добавил ответ на этот вопрос. Даже если у нас есть T(const &), он может определить, существует ли T(T&&). - person Aaron McDaid; 08.11.2013
comment
@AaronMcDaid: Очень хорошо - две базы - это то, что нужно :-) - person Kerrek SB; 08.11.2013

Обновление 3: я добавил еще один ответ, поэтому игнорируйте его. У меня возникает соблазн удалить это, поскольку оно больше не работает для меня с более новыми компиляторами. Но у меня уже есть несколько ответов здесь, так что я не должен удалять это. Кроме того, этот конкретный ответ работал на некоторых старых компиляторах, поэтому он может быть полезен для некоторых людей.

Это проверит, есть ли конструктор формы T(T&&). Работает на clang-3.3 и g++-4.6.3. Но этот тест на ideone показывает, что их компилятор (g++-???) путает конструкторы копирования и перемещения.< /эм>

Обновление 2: январь 2015 г.. Это не работает с более новыми версиями g++ (4.8.2) и clang (3.5.0). Поэтому я думаю, что мой ответ здесь бесполезен, если сначала не проверить, поддерживает ли ваша конкретная версия трюк, который я использовал здесь. Возможно, мой трюк не соответствует стандарту и с тех пор был удален из g++ и clang++. В моем ответе ниже я сказал, что производный класс будет иметь неявный конструктор перемещения только в том случае, если все его базы имеют конструкторы перемещения - возможно, это неверно или слишком упрощенно?

struct move_not_copy { move_not_copy(move_not_copy &&); };

template<typename T>
struct has_move_constructor {
        struct helper : public move_not_copy,  public T {
        };
        constexpr static bool value =
               std::is_constructible<helper,
               typename std::add_rvalue_reference<helper>::type> :: value;
        constexpr operator bool () const { return value; }
};

Точнее, независимо от того, есть ли у класса конструктор копирования T(const T&), этот трейт по-прежнему может определять, есть ли у класса также конструктор перемещения T(T&&).

Хитрость заключается в том, чтобы получить очень простой класс helper с двумя базами и без других методов/конструкторов. Такой производный класс будет иметь неявный конструктор перемещения только в том случае, если все его базовые элементы имеют конструкторы перемещения. Аналогично для конструкторов копирования. Первая база, move_not_copy, не имеет конструктора копирования, поэтому helper не будет иметь конструктора копирования. Однако helper по-прежнему может использовать неявно определенный конструктор перемещения, если и только если T имеет такой конструктор. Следовательно, у helper будет либо ноль конструкторов, либо один конструктор (конструктор перемещения), в зависимости только от того, есть ли у T конструктор перемещения.


Тесты. Это таблица для четырех типов, показывающая желаемое поведение. Полная программа тестирования находится по адресу ideone, но, как я уже говорил, она дает неверные результаты на ideone, потому что они пользуетесь и старым g++.

               Copy is_copy_constructible 1  is_move_constructible 1  has_move_constructor 0
           MoveOnly is_copy_constructible 0  is_move_constructible 1  has_move_constructor 1
               Both is_copy_constructible 1  is_move_constructible 1  has_move_constructor 1
CopyWithDeletedMove is_copy_constructible 1  is_move_constructible 0  has_move_constructor 0

Что стандарт говорит по этому поводу? Я понял эту идею после прочтения cppreference, а именно:

Неявно объявленный или заданный по умолчанию конструктор перемещения для класса T определяется как удаленный, если верно любое из следующего:

...

T имеет прямой или виртуальный базовый класс, который нельзя переместить (имеет удаленные, недоступные или неоднозначные конструкторы перемещения)

...

и я предполагаю, что то же самое относится и к конструкторам копирования.

person Aaron McDaid    schedule 08.11.2013
comment
Пробовал это с помощью coliru с clang 3.5 и не увидел там ожидаемого поведения. struct Foo { int n; ~Foo() {n=0;} Foo& operator=(const Foo& rhs) { n=rhs.n; return *this;} }; Я ожидаю, что has_move_constructor‹Foo› будет ложным, учитывая его пользовательский деструктор и operator=. Однако он возвращает true. - person Cross_; 04.01.2015
comment
Согласовано. Мой код работал с clang 3.3, который я использовал, когда отвечал на этот вопрос, но с clang 3.5 он больше не работает. Произошли некоторые изменения в clang. Это также не работает с g++-4.8.2. Я не знаю, что такое «правильное» поведение в соответствии со стандартом. Компиляторы становятся более или менее совместимыми. Я поставлю примечание в начале своего ответа. - person Aaron McDaid; 05.01.2015

Вы можете ввести преднамеренную ошибку двусмысленности, когда присутствуют и конструктор перемещения, и конструктор копирования. Это позволяет нам проверить наличие конструктора перемещения.

В последние годы по мере изменения компиляторов разные решения работают, а затем ломаются. Это работает с clang 3.5.0. Я надеюсь, что он будет работать и на старых, и на новых компиляторах, но я не эксперт по стандарту.

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

Во-первых, легко сказать, есть ли копирующий конструктор. Если конструктора копирования нет, то легко определить, есть ли конструктор перемещения. Проблема состоит в том, что при наличии есть конструктора копирования проверить, есть ли также конструктор перемещения. Это задача, на которой я сосредоточусь здесь.

Поэтому достаточно рассматривать только типы, у которых есть конструктор копирования, и проверять наличие конструктора перемещения. В оставшейся части этого вопроса я предполагаю наличие конструктора копирования.


Я проверяю конструктор перемещения, вызывая ошибку неоднозначности, когда присутствуют оба вида конструктора, а затем (ab) использую SFINAE для проверки наличия этой неоднозначности.

Другими словами, наша задача состоит в том, чтобы проверить разницу между следующими типами:

struct CopyOnly { 
    CopyOnly (const CopyOnly&);  // just has a copy constructor
};
struct Both { 
    Both (const Both&);          // has both kinds of constructor
    Both (Both&&);     
};

Для этого сначала определите класс Converter<T>, который утверждает, что может преобразовывать себя в два вида ссылок. (Нам никогда не понадобится их реализовывать)

template<typename T>
struct Converter { 
    operator T&& ();
    operator const T& ();
};

Во-вторых, рассмотрите эти строки:

Converter<T> z;
T t(z);

Вторая строка пытается построить T. Если T равно CopyOnly, то t будет выполнено через конструктор копирования, а соответствующая ссылка для передачи конструктору копирования будет извлечена из метода operator const CopyOnly &() Converter<CopyOnly>. Пока это довольно стандартно. (Я думаю?)

Но если T равно Both, т. е. у него также есть конструктор перемещения, будет ошибка неоднозначности. Доступны оба конструктора T, поскольку для обоих доступны преобразователи (преобразователи из z), поэтому возникает неоднозначность. (Кто-нибудь из языковых юристов может подтвердить, что это полностью стандартно?)

Эта логика применима и к new T( Converter<T>{} ). Это выражение имеет тип тогда и только тогда, когда T не имеет конструктора перемещения. Поэтому мы можем обернуть decltype вокруг этого и использовать это в SFINAE.

Я закрываю двумя перегрузками baz<T>. Выбранная перегрузка будет зависеть от того, соответствует ли T CopyOnly или Both. Первая перегрузка действительна только в том случае, если new T( Converter<T>{} ) определено корректно, т. е. если нет ошибки неоднозначности, т. е. если нет конструктора перемещения. Вы можете указать разные типы возврата для каждой перегрузки, чтобы сделать эту информацию доступной во время компиляции.

template<typename T>
std:: true_type
baz (decltype( new T( Converter<T>{} )   )) {
    cout << __LINE__ << endl;
    return {};
}

template<typename U>
std:: false_type
baz ( 
        const volatile // const volatile to tie break when both forms of baz are available
        U *) { 
    cout << __LINE__ << endl;
    return {};
}

baz следует вызывать так:

baz<JustCopy>((JustCopy*)nullptr);
baz<Both>((Both*)nullptr);

И вы могли бы обернуть это как-то так:

template<typename T>
struct has_move_constructor_alongside_copy {
    typedef decltype(baz<T>((T*)nullptr)) type;
};

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

person Aaron McDaid    schedule 08.01.2015
comment
Я не думаю, что это больше работает, не так ли? У меня нет двусмысленности в MSVC 2019 :\ - person user541686; 16.12.2020

Я взял последний ответ Аарона МакДейда и заключил его в приведенную ниже конструкцию. Код в его ответе у меня не сработал, это работает как с clang 3.6, так и с MSVC2013.

template <typename T>
struct has_move_constructor_alongside_copy {
  typedef char yes[1];
  typedef char no[2];

  struct AmbiguousConverter {
    operator T&& ();
    operator const T& ();
  };

  template <typename C>
  static no& test(decltype( new C( AmbiguousConverter{} )));

  template <typename>
  static yes& test(...);

  static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
person Bas Zalmstra    schedule 12.06.2015
comment
Кажется, это не работает для меня в VC2013; он возвращает false для конструкторов по умолчанию (плохо), false для только копирования (хорошо), true для удаленного перемещения (плохо), true для только перемещения (хорошо) и false для перемещения и копирования (плохо). - person Miral; 04.11.2016