Тип для подвижных типов?

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

Любая помощь была оценена.

Ответ 1

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

Пока <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

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

Ответ 3

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

Обновление 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

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

В последние годы, когда компиляторы меняются, разные решения работают, а затем ломаются. Это работает с 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>{} ). Это выражение имеет тип if, и только если 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

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