Я пытаюсь написать шаблон, который ведет себя одним образом, если у T есть конструктор перемещения, и другим, если у T его нет. Я пытался найти черту типа, которая могла бы идентифицировать это, но мне не повезло, и мои попытки написать собственную черту типа для этого потерпели неудачу.
Любая помощь приветствуется.
Не могли бы вы немного пояснить свой вопрос? Даже если для типа не определен явный конструктор перемещения, обычно его можно сконструировать как T t2{ std:: move(t1) };. Это выражение вернется к конструктору копирования, если он присутствует. Итак, что именно вы спрашиваете? Вы хотите конкретно проверить, был ли определен явный конструктор перемещения (довольно узкий тест)? Или, в более широком смысле, вы хотите проверить, может ли он быть построен из r-значения (легко проверить, будет включать почти все типы, у которых есть конструктор копирования)? Aaron McDaid 09.01.2015
Чтобы упростить мой вопрос: я думаю, я спрашиваю о ваших словах ... ведет себя так, если T имеет конструктор перемещения.... Возможно, вы имеете в виду, что ... ведет себя так, если тип T имеет явно определенный конструктор с сигнатурой T :: T(T&&)... Aaron McDaid 09.01.2015
Я чувствую необходимость указать на тонкое различие.
Хотя <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 только тогда, когда класс имеет конструктор перемещения:
Обратите внимание, что черта компилятора 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++.
Что стандарт говорит по этому поводу? Я понял эту идею после прочтения 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>, который утверждает, что может преобразовывать себя в два вида ссылок. (Нам никогда не понадобится их реализовывать)
Вторая строка пытается построить T. Если T равно CopyOnly, то t будет выполнено через конструктор копирования, а соответствующая ссылка для передачи конструктору копирования будет извлечена из метода operator const CopyOnly &()Converter<CopyOnly>. Пока это довольно стандартно. (Я думаю?)
Но если T равно Both, т. е. у него также есть конструктор перемещения, будет ошибка неоднозначности. Доступны оба конструктора T, поскольку для обоих доступны преобразователи (преобразователи из z), поэтому возникает неоднозначность. (Кто-нибудь из языковых юристов может подтвердить, что это полностью стандартно?)
Эта логика применима и к new T( Converter<T>{} ). Это выражение имеет тип тогда и только тогда, когда T не имеет конструктора перемещения. Поэтому мы можем обернуть decltype вокруг этого и использовать это в SFINAE.
Я закрываю двумя перегрузками baz<T>. Выбранная перегрузка будет зависеть от того, соответствует ли TCopyOnly или 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 {};
}
Нужно многое сделать, чтобы привести его в порядок, и я уверен, что эксперты SFINAE могли бы значительно его улучшить (пожалуйста, сделайте!). Но я думаю, что это решает основную проблему, проверяя наличие конструктора перемещения, когда мы уже знаем, что конструктор копирования присутствует.
Ответ 6 · рейтинг 1 · Bas Zalmstra
Я взял последний ответ Аарона МакДейда и заключил его в приведенную ниже конструкцию. Код в его ответе у меня не сработал, это работает как с clang 3.6, так и с MSVC2013.