Типовая черта для подвижных типов? - PullRequest
14 голосов
/ 14 августа 2011

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

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

Ответы [ 6 ]

18 голосов
/ 14 августа 2011

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

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

9 голосов
/ 14 августа 2011

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

3 голосов
/ 14 августа 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 и должен быть предоставлен отдельно, см. Мой полный код .

2 голосов
/ 08 ноября 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 определяется как удаленный, если any из следующих условий имеет значение true:

...

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

...

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

1 голос
/ 12 июня 2015

Я взял Последний ответ Аарона МакДейда и завернул его в конструкцию ниже.Код в его ответе не работает для меня, это касается и 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);
};
1 голос
/ 09 января 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 могли бы значительно его улучшить (пожалуйста!). Но я думаю, что это решает основную проблему, проверяя наличие конструктора перемещения, когда мы уже знаем, присутствует ли конструктор копирования.

...