Проблема с шаблоном С++ с добавлением двух типов данных
У меня есть шаблонный класс с перегруженным + оператором. Это нормально работает, когда я добавляю два ints или два двухлокальных. Как я могу его добавить, а int и double и вернуть double?
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;
Ответы
Ответ 1
Здесь будут драконы. Вы получаете в часть С++, которые, вероятно, приведут к большому количеству вопросов, опубликованного в 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
. Ошибочные библиотеки делают для печальных пользователей. Как лучше справиться с этим? Я не знаю...
Ответ 2
Стивен уже дал хорошее объяснение проблем, с которыми вы можете столкнуться. Вы можете определить перегрузки для всех возможных комбинаций всех экземпляров шаблона (так что вы бы эффективно определили операторы для 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;
С++ 0x добавит поддержку ряда языковых функций, которые сделают это намного проще и позволят вам написать разумную перегрузку operator+
:
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);
}
Это обеспечит выполнение обычных арифметических преобразований (целочисленное продвижение, преобразование в плавающую точку и т.д.), и вы получите ожидаемый тип результата.
Ваша реализация на С++ может уже поддерживать эти возможности С++ 0x; вы хотите ознакомиться с документацией о любом компиляторе, который вы используете.
Ответ 3
Я хочу также сделать это
std:: cout < doubleTt1 + intTt2 < "\ П";
В этом случае вам понадобится тип. По сути, это классы шаблонов, содержащие typedef
s. Вы затем частично специализируете такой шаблон, чтобы переопределить typedef
s.
Основной пример:
(Вероятно, это немного наивно, но он должен получить основную идею.)
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.: Исправления, сделанные в соответствии с приведенными ниже комментариями.
Ответ 4
Оператор добавления обычно должен быть свободной функцией, чтобы избежать предпочтения любого типа операнда, поскольку @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
. В качестве альтернативы, как показано в ответе promote<>
, вы также можете специализировать promote
, чтобы вы могли применить его непосредственно к типам TemplateTest
.
Ответ 5
Если это в основном для базовых типов, вы можете помочь себе с метафоном до тех пор, пока новый стандарт не вступит. Что-то в строках
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.
О, и в производственном коде вы, вероятно, захотите правильно создать пространство имен и добавить что-то для несоответствий с подписью/без знака в целых типах.
Ответ 6
Получите компилятор, который поддерживает новый оператор объявления типа С++ 0x.
template < typename T1, typename T2 >
auto add(T1 t1, T2 t2) -> decltype(t1+t2)
{
return t1 + t2;
}
Теперь вам не нужно перемещаться с этими классами "черт".
Ответ 7
Вы можете добавить значения int и double с помощью шаблонов. В функции укажите 2 аргумента и при передаче значений в функции укажите его типы в скобках angular.
Пример:
//template
template<typename T1, typename T2>
void add(T1 a, T2 b)
{
//for adding values
cout<<"\n\nSum : "<<a+b;
}
int main ()
{
//specify types while passing values to funcion
add<int,double>(4,5.5454);
add<float,int>(4.7,5);
add<string,string>("hi","bye");
return 0;
}
Ответ 8
Это технически возможно, указав неявный случай на TemplateTest<double>
:
operator TemplateTest<double>() {
return TemplateTest<double>((double)x);
}
Практически это, вероятно, не очень хорошая идея, так как x
не может быть безопасно применено к двойнику; бывает, что вы используете TemplateTest<int>
здесь, но позже вы можете использовать TemplateTest<std::string>
. Вероятно, вам следует пересмотреть то, что вы делаете, и решить, уверены ли вы, что вам действительно нужно это поведение.
Ответ 9
Более новый ответ на старый вопрос.
Для С++ 0x вы можете пойти с decltype
, как говорили другие ответы.
Я бы сказал, что common_type
больше подходит для ситуации, чем decltype
.
Ниже приведен пример common_type
, используемый в общем добавлении:
#include <iostream>
#include <array>
#include <type_traits>
using namespace std;
template <typename T, typename U, unsigned long N>
array<typename common_type<T, U>::type, N> // <- Gets the arithmetic promotion
add_arrays(array<T, N> u, array<U, N> v)
{
array<typename common_type<T, U>::type, N> result; // <- Used again here
for (unsigned long i = 0; i != N; ++i)
{
result[i] = u[i] + v[i];
}
return result;
}
int main()
{
auto result = add_arrays( array<int, 4> {1, 2, 3, 4},
array<double, 4> {1.0, 4.23, 8.99, 55.31} );
for (size_t i = 0; i != 4; ++i)
{
cout << result[i] << endl;
}
return 0;
}
он в основном возвращает значение, которое будут способствовать различные арифметические операции. Одна хорошая вещь в том, что он может принимать любое количество шаблонов args. Примечание. Не забудьте добавить ::type
в конец. Это то, что получает фактический результат типа, который вы хотите.
Для тех, кто работает с pre-С++ 11, есть также версия boost common_type
, а также