Перегрузка арифметического оператора С++ — автоматическое расширение?

У меня есть класс Vector, который представляет 2D-вектор. Он создан по шаблону, позволяющему использовать любой числовой тип для компонентов x и y. Например, один из перегруженных арифметических операторов — * для умножения вектора на скаляр:

template <typename T, typename U>
inline const Vector<T> operator*(const Vector<T>& vector, U scalar) {
    return Vector<T>(vector.x * scalar, vector.y * scalar);
}

(У меня также есть функция с параметрами в обратном порядке, чтобы разрешить scalar * Vector в дополнение к Vector * scalar).

Как видите, я использую <T, U> вместо простого <T>, чтобы скаляр не обязательно был того же типа, что и вектор. Когда я не сделал этого, неожиданно Vector<double> * int не компилировался (я думал, что int автоматически расширится).

В любом случае, я не просто хочу вернуть Vector<T>. Я хочу имитировать встроенные типы и возвращать то, что имеет более высокую точность, T или U. Так, например, Vector<int> * double => Vector<double>, а Vector<double> * short => Vector<double>.

Это возможно?


person mk12    schedule 20.02.2012    source источник


Ответы (2)


Есть два решения. В Pre C++11 вы можете написать такой шаблон:

template <typename T, typename U>
struct WhatWillItBe {
  typedef T result_t;
};

template <typename T>
struct WhatWillItBe<T, double> {
  typedef double result_t;
};

// ... lots more

и т. д. и напишите много специализаций, затем вы можете использовать это для поиска возвращаемого типа, например:

template <typename T, typename U>
inline const Vector<typename WhatWillItBe<T,U>::result_t> operator*(const Vector<T>& vector, U scalar) {
    return Vector<typename WhatWillItBe<T,U>::result_t>(vector.x * scalar, vector.y * scalar);
}

В качестве альтернативы С++ 11 делает это простым, вы можете написать auto для типа возвращаемого значения и использовать -> для указания типа возвращаемого значения после остальной части функции:

template <typename T, typename U>
inline auto operator*(const Vector<T>& vector, U scalar) -> Vector<decltype(vector.x*scalar)> {
    return Vector<decltype(vector.x*scalar)>(vector.x * scalar, vector.y * scalar);
}

Что позволяет вам использовать decltype для возвращаемого типа функции, устанавливая его на основе того, что произойдет для продвижения естественным образом с vector.x * scalar.

person Flexo    schedule 20.02.2012
comment
Круто, хотел бы я проголосовать больше, чем один раз. Я все время вижу хорошие применения синтаксиса возвращаемого типа ->. - person Seth Carnegie; 21.02.2012
comment
Заметим, конечно, что в C++11 тип возвращаемого значения не должен быть константным, так как это препятствует семантике перемещения. - person ildjarn; 21.02.2012
comment
Clang++ 3.0 поддерживает это? - person mk12; 21.02.2012
comment
@Mk12 — похоже, согласно clang.llvm.org/cxx_status.html (новый синтаксис декларатора функции) - person Flexo; 21.02.2012
comment
Вы правы, ваш последний пример компилируется, когда я использую clang++ -std=c++0x :). - person mk12; 21.02.2012
comment
@ildjarn: Не могли бы вы объяснить, почему это так (или дать ссылку)? Я только что закончил читать в Effective C++, как возвращаемый тип должен быть const - person mk12; 21.02.2012
comment
@ Mk12 - конструктор / присваивание перемещения не может принимать const по определению - он изменит (и уничтожит) объект. - person Flexo; 21.02.2012
comment
@awoodland: Что такое конструктор перемещения? Я никогда раньше не слышал этого термина. Это специфично для С++ 11? - person mk12; 21.02.2012
comment
@Mk12 Mk12: Эффективный C++ был написан задолго до C++11; если когда-нибудь появится новое издание, эта рекомендация обязательно будет удалена. - person ildjarn; 21.02.2012

Вы можете использовать common_type или decltype, чтобы приготовить что-то, что даст вам результирующий тип; а затем вам нужно создать фактический вектор:

template <typename A, typename B>
std::vector<typename std::common_type<A, B>::type>
operator*(std::vector<A> const & v, B const & x)
{
    std::vector<typename std::common_type<A, B>::type> res;
    res.reserve(v.size());
    for (A a : v) res.push_back(a * x);
    return res;
}

Используя decltype, вы можете получить тип результата через:

decltype(std::declval<A>() * std::declval<B>())

Для std::common_type и std::declval вам нужно #include <type_traits>.

С типами отложенного возврата (auto и ->) вы можете использовать decltype непосредственно в аргументах функции, но использование std::declval кажется немного более гигиеничным, так как вам не требуется предоставлять фактический экземпляр вашего типа (и, таким образом, это применимо даже в ситуациях, когда это невозможно).

person Kerrek SB    schedule 20.02.2012
comment
Раньше я не видел common_type! Это довольно удобно. - person Flexo; 21.02.2012
comment
declval тоже удобно, +1 - person Seth Carnegie; 21.02.2012
comment
Кстати, я говорил о своем собственном математическом классе Vector, а не о std::vector, но это все равно отличный ответ. К сожалению, я получаю сообщение об ошибке, когда #include выполняю <type_traits>, поэтому я думаю, что это не сработает для меня. - person mk12; 21.02.2012
comment
Следует отметить, что для этого ответа требуется С++ 11; если OP ограничен C ++ 03, здесь мало помощи. - person ildjarn; 21.02.2012
comment
@Mk12: эти трейты являются только частью C++11, поэтому добавьте -std=c++0x в GCC или используйте MSVS 10 и выше. - person Kerrek SB; 21.02.2012
comment
Старый добрый C++, 4 способа сделать одно и то же… Мне больше всего нравится решение common_type, но оно мне недоступно. Однако этот ответ в равной степени заслуживает того, чтобы быть принятым ответом. - person mk12; 21.02.2012