У меня есть шаблонный класс с перегруженным + оператор. Это хорошо работает, когда я добавляю, что два ints или два удваиваются. Как я заставляю это добавлять и интервал и двойное и возвращать двойное?
template <class T>
class TemplateTest
{
private:
T x;
public:
TemplateTest<T> operator+(const TemplateTest<T>& t1)const
{
return TemplateTest<T>(x + t1.x);
}
}
in my main function i have
void main()
{
TemplateTest intTt1 = TemplateTest<int>(2);
TemplateTest intTt2 = TemplateTest<int>(4);
TemplateTest doubleTt1 = TemplateTest<double>(2.1d);
TemplateTest doubleTt2 = TemplateTest<double>(2.5d);
std::cout << intTt1 + intTt2 << /n;
std::cout << doubleTt1 + doubleTt2 << /n;
}
Я хочу смочь также сделать это
std::cout << doubleTt1 + intTt2 << /n;
Стивен уже дал хорошее объяснение проблем, с которыми вы можете столкнуться при этом. Вы можете определить перегрузки для всех возможных комбинаций всех экземпляров шаблона (таким образом, у вас фактически будут операторы, определенные для double + double, int + double, double + int и т. Д.).Это может быстро стать громоздким, и может быть сложно отследить, какие комбинации поддерживаются.
Возможно, вам лучше использовать функцию, не являющуюся членом, с именем вроде Add ()
. Преимущество этого заключается в том, что вы можете указать тип возвращаемого значения. Недостаток в том, что вам нужно указывать возвращаемый тип. :-) В общем, это лучше, чем автоматическое выполнение неожиданных преобразований.
template <typename R, typename T, typename U>
TemplateTest<R> Add(const TemplateTest<T>& t, const TemplateTest<U>& u)
{
return TemplateTest<R>(t.x + u.x);
}
вызывается как:
std::cout << Add<double>(intTt1, doubleTt1) << std::endl;
C ++ 0x добавит поддержку ряда языковых функций, которые сделают это намного проще и позволят вам написать разумную перегрузку оператора +
:
template <typename T, typename U>
auto operator+(const TemplateTest<T>& t, const TemplateTest<U>& u)
-> TemplateTest<decltype(t.x + u.x)>
{
return TemplateTest<decltype(t.x + u.x)>(t.x + u.x);
}
Это будет убедитесь, что выполняются обычные арифметические преобразования (целочисленное преобразование, преобразование в числа с плавающей запятой и т. д.), и вы получаете ожидаемый тип результата.
Ваша реализация C ++ может уже поддерживать эти функции C ++ 0x; вы бы хотели проконсультироваться с документацией любого используемого вами компилятора.
Получите компилятор, поддерживающий новый оператор decltype C ++ 0x.
template < typename T1, typename T2 >
auto add(T1 t1, T2 t2) -> decltype(t1+t2)
{
return t1 + t2;
}
Теперь вам не нужно возиться с этими "чертами" классов.
Оператор сложения обычно должен быть бесплатной функцией, чтобы не отдавать предпочтение любому типу операнда, как красиво объясняет @Stephen. Таким образом, он полностью симметричен. Предполагая, что у вас есть функция get
, которая возвращает сохраненное значение, это может выглядеть следующим образом (в качестве альтернативы вы можете объявить его как друга, если такая функция get
не существует)
template<typename T1, typename T2>
TemplateTest<???> operator+(const TemplateTest<T1>& t1, const TemplateTest<T2>& t2)
{
return TemplateTest<???>(t1.get() + t2.get());
}
Теперь проблема состоит в том, чтобы найти тип результата. Как показывают другие ответы, это возможно с помощью decltype
в С ++ 0x. Вы также можете смоделировать это, используя правила оператора ?:
, которые в большинстве своем довольно интуитивно понятны. promotion <> - это шаблон, который использует эту технику
template<typename T1, typename T2>
TemplateTest< typename promote<T1, T2>::type >
operator+(const TemplateTest<T1>& t1, const TemplateTest<T2>& t2)
{
return TemplateTest< typename promote<T1, T2>::type >(t1.get() + t2.get());
}
Теперь, например, если вы добавите double
и int
, это даст double
] в результате. В качестве альтернативы, как показано в ответе продвижение <>
, вы также можете специализировать продвижение
, чтобы применить его непосредственно к типам TemplateTest
.
Если это в основном для базовых типов, вы можете помочь себе метафункцией, пока не появится новый стандарт. Что-то вроде
template<typename T1,
typename T2,
bool T1_is_int = std::numeric_limits<T1>::is_integer,
bool T2_is_int = std::numeric_limits<T2>::is_integer,
bool T1_is_wider_than_T2 = (sizeof(T1) > sizeof(T2)) > struct map_type;
template<typename T1, typename T2, bool b> struct map_type<T1, T2, b, b, true > { typedef T1 type; };
template<typename T1, typename T2, bool b> struct map_type<T1, T2, b, b, false> { typedef T2 type; };
template<typename T1, typename T2, bool b> struct map_type<T1, T2, false, true , b> { typedef T1 type; };
template<typename T1, typename T2, bool b> struct map_type<T1, T2, true , false, b> { typedef T2 type; };
template<typename T, typename U>
typename map_type<TemplateTestT<T>, TemplateTest<U> >::type
operator+(TemplateTest<T> const &t, TemplateTest<U> const &u) {
return typename map_type<TemplateTest<T>, TemplateTest<U> >::type(x + t1.x);
}
Конечно, это лучше всего сочетать с идеей char_traits:
template <typename A, typename B>
struct add_traits
{
typedef A first_summand_t;
typedef B second_summand_t;
typedef typename map_type<A, B>::type sum_t;
};
Так вы сможете специализировать типы, у которых нет перегрузки numeric_limits.
О, и в производственном коде вы, вероятно, захотите правильно распределить имена и добавить что-то для несоответствия signed/unsigned в целочисленных типах.
Here be dragons. Вы вникаете в те части c++, которые, вероятно, приведут к большому количеству вопросов, размещенных на StackOverflow :) Подумайте хорошенько, действительно ли вы хотите этим заниматься.
Начните с самого простого: вы хотите разрешить operator+
добавлять типы, которые не всегда совпадают с T
. Начните с этого:
template <typename T2>
TemplateTest<T> operator+(const TemplateTest<T2>& rhs) {
return TemplateTest<T>(this->x + rhs.x);
}
Обратите внимание, что это шаблон на T2
, а также на T
. При сложении doubleTt1 + intTt2
, T
будет doubleTt1
, а T2
будет intTt2
.
Но вот большая проблема со всем этим подходом.
Теперь, когда вы складываете double
и int
, что вы ожидаете? 4 + 2.3 = 6.3
? или 4 + 2.3 = 6
? Кто мог ожидать 6
? Ваши пользователи должны, потому что вы приводите двойное число обратно к int
, теряя дробную часть. Иногда. В зависимости от того, какой операнд стоит первым. Если пользователь написал 2.3 + 4
, он получит (как и ожидалось?) 6.3
. Запутанные библиотеки делают пользователей грустными. Как лучше с этим бороться? Не знаю...
Я хочу также иметь возможность делать вот это
std::cout << doubleTt1 + intTt2 << "\n";
То, что вам, вероятно, понадобится в этом случае - это type traits. По сути, это шаблонные классы, содержащие typedef
ы. Затем вы частично специализируете такой шаблон, чтобы переопределить typedef
ы.
(Возможно, это немного наивный пример, но он должен донести основную идею. )
template <typename A, typename B>
struct add_traits
{
typedef A first_summand_t; // <-- (kind of an "identity type")
typedef B second_summand_t; // <-- (ditto; both aren't strictly necessary)
typedef B sum_t; // <-- this is the interesting one!
};
Теперь вы частично специализируете эту штуку для различных комбинаций A
и B
:
template<>
struct add_traits<int, int>
{
typedef int first_summand_t;
typedef int second_summand_t;
typedef int sum_t; // <-- overrides the general typedef
};
template<>
struct add_traits<int, double>
{
typedef int first_summand_t;
typedef double second_summand_t;
typedef double sum_t; // <-- overrides the general typedef
};
template<>
struct add_traits<double, int>
{
typedef double first_summand_t;
typedef int second_summand_t;
typedef double sum_t; // <-- overrides the general typedef
};
Теперь вы можете написать достаточно общую операцию добавления, которая будет выглядеть так:
template <typename A, typename B>
typename add_traits<A,B>::sum_t add(A first_summand, B second_summand)
{
// ...
}
Как вы видите, вы не указываете конкретный возвращаемый тип; вместо этого вы позволяете компилятору выяснить это через add_traits
шаблонный класс. Когда компилятор сгенерирует код для конкретной add
функции, он найдет типы в соответствующем классе add_traits
, и благодаря частично специализированным версиям, которые вы предоставили, вы можете быть уверены, что будут применены определенные "комбинации" типов.
P.S.: Эта же техника была бы полезна, например, при вычитании беззнаковых чисел. Вычитание одного unsigned int
из другого может привести к отрицательному ответу; тип результата должен быть (signed
) int
.
P.P.S.: Исправления внесены в соответствии с комментариями ниже.
Технически это возможно, если определить неявный case для TemplateTest
:
operator TemplateTest<double>() {
return TemplateTest<double>((double)x);
}
Практически это, вероятно, не очень хорошая идея, так как x
не обязательно может быть безопасно приведено к double; так получилось, что вы используете TemplateTest
здесь, но вы могли бы использовать TemplateTest
позже. Возможно, вам следует переосмыслить то, что вы делаете, и решить, уверены ли вы, что вам действительно нужно такое поведение