Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: KranarОтветов: 6

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

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

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

c++c++11typetraits

Ответы

Ответ 1 · рейтинг 19 · Luc Danton

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

Хотя <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 не подходит для ваших целей.

Ответ 2 · рейтинг 9 · Nicol Bolas

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

Ответ 3 · рейтинг 3 · Kerrek SB

После небольшого обсуждения и полного согласия с тем, что это может быть совершенно бесполезным, и с предупреждением о том, что старые компиляторы могут сделать это неправильно, я, тем не менее, хотел бы вставить небольшой класс признаков, который я смонтировал, и я верю, что он даст вам 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 и должна предоставляться отдельно, см. мой полный код.

Ответ 4 · рейтинг 2 · Aaron McDaid

Обновление 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 имеет прямой или виртуальный базовый класс, который нельзя переместить (имеет удаленные, недоступные или неоднозначные конструкторы перемещения)

...

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

Ответ 5 · рейтинг 1 · Aaron McDaid

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

В последние годы по мере изменения компиляторов разные решения работают, а затем ломаются. Это работает с 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 могли бы значительно его улучшить (пожалуйста, сделайте!). Но я думаю, что это решает основную проблему, проверяя наличие конструктора перемещения, когда мы уже знаем, что конструктор копирования присутствует.

Ответ 6 · рейтинг 1 · Bas Zalmstra

Я взял последний ответ Аарона МакДейда и заключил его в приведенную ниже конструкцию. Код в его ответе у меня не сработал, это работает как с 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);
};

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы