Типовая черта для подвижных типов?
Я пытаюсь написать шаблон, который ведет себя одним образом, если T имеет конструктор перемещения, и другим способом, если T не имеет. Я пытался найти черту типа, которая могла бы идентифицировать это, но мне не повезло, и мои попытки написать собственную черту для этого не увенчались успехом.
Любая помощь приветствуется.
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
не подходит для ваших целей, однако.
Это называется std::is_move_constructable
, Существует также std::is_move_assignable
, Они оба в C++0x <type_traits>
заголовок.
После небольшого обсуждения и полного согласия с тем, что это может быть совершенно бесполезным, и с предупреждением о том, что старые компиляторы могут ошибаться, я все же хотел бы вставить небольшой класс черт, который я подстроил, и который, я считаю, даст вам 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 и должен быть предоставлен отдельно, см. мой полный код.
Обновление 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 имеет прямой или виртуальный базовый класс, который не может быть перемещен (имеет удаленные, недоступные или неоднозначные конструкторы перемещения)
...
и я предполагаю, что аналогичная вещь применима к конструкторам копирования.
Вы можете ввести преднамеренную ошибку неоднозначности, когда присутствуют как конструктор перемещения, так и конструктор копирования. Это позволяет нам проверять наличие конструктора перемещения.
В последние годы, когда меняются компиляторы, разные решения работают, а затем ломаются. Это работает с 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 могли бы значительно улучшить его (пожалуйста!). Но я думаю, что это решает основную проблему, проверяя наличие конструктора перемещения, когда мы уже знаем, присутствует ли конструктор копирования.
Я взял последний ответ Аарона МакДейда и завернул его в конструкцию ниже. Код в его ответе не работает для меня, это работает как с 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);
};