Проблема с шаблоном С++ с добавлением двух типов данных

У меня есть шаблонный класс с перегруженным + оператором. Это нормально работает, когда я добавляю два 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, а также