Ответ 1
Пока я не вижу в вашей реализации неотъемлемого понятия, я бы сделал три предложения.
(note:). Здесь я расскажу о реализации концепции обменивания rvalues как swap_rvalues
)
Исключить типы const из вычитания шаблона
- Как?
Предполагая
template <typename A, typename B>
struct is_same_no_ref
: std::is_same<
typename std::remove_reference<A>::type,
typename std::remove_reference<B>::type
>
{};
измените условие включения из std::enable_if<std::is_same_no_ref<A, B>::value>
на следующее.
std::enable_if<
std::is_same_no_ref<A, B>::value &&
!std::is_const<typename std::remove_reference<A>::type>::value &&
!std::is_const<typename std::remove_reference<B>::type>::value
>
- Почему?
Без исключения const-типов из вывода шаблона, передавая константные переменные в swap_rvalues
, как показано ниже,
int const a = 0, b = 0;
swap_rvalues(a, b);
побуждает компилятор отмечать ошибки, связанные с деталями внутренней реализации, что не очень удобно.
Переместить условие включения в декларацию типа возврата
- Как?
Вместо
template<typename A, typename B, typename = typename std::enable_if<...>::type>
inline void swap_rvalues(A&& a, B&& b);
объявите его следующим образом
template<typename A, typename B>
inline typename std::enable_if<...>::type swap_rvalues(A&& a, B&& b);
- Почему?
Несмотря на то, что очень маловероятно, возможно явное определение третьего параметра шаблона swap_rvalues
, эффективно перекрывая условие включения. Это может привести к компиляции кода, который не должен и nastiness может следовать. Этого полностью избежать можно с помощью объявления типа возврата для условия включения.
Рассмотрим следующий пример.
template <
typename A,
typename B,
typename = typename std::enable_if<
is_same_no_ref<A, B>::value &&
!std::is_const<typename std::remove_reference<A>::type>::value &&
!std::is_const<typename std::remove_reference<B>::type>::value
>::type>
inline void
swap(A&& a, B&& b) {
typename std::remove_reference<A>::type t = std::move(a);
a = std::move(b);
b = std::move(t);
}
struct B;
struct A{A(){} A(B const&){}};
struct B{B(){} B(A const&){}};
swap<A, B, void>(A(), B());
Он компилируется, хотя он явно не должен! A
и B
даже не связаны друг с другом, они просто становятся конструктивными, если ссылаются на другие.
Код повторного использования [aka KISS]
- Как?
Поскольку значения r уже присвоены имени, просто переадресуйте вызов std::swap
вместо того, чтобы обеспечить новую реализацию swap_rvalues
.
- Почему?
Почему изобретать колесо? std::swap
уже предоставляет предполагаемое поведение, когда rvalues присваивается имя, поэтому почему бы не повторно использовать его?
Заключение
Окончательная реализация swap_rvalues
‡ будет выглядеть следующим образом.
template <typename A, typename B>
inline typename std::enable_if<
is_same_no_ref<A, B>::value &&
!std::is_const<typename std::remove_reference<A>::type>::value &&
!std::is_const<typename std::remove_reference<B>::type>::value
>::type
swap_rvalues(A&& a, B&& b) {
std::swap(a, b);
}
Сноска
† "К изобретать колесо - это дублировать базовый метод, который ранее был создан или оптимизирован другими."
‡ swap_rvalues
на самом деле лучше будет называться swap
в реальном сценарии.