Шаблонная проблема C++, добавляющая два типа данных

У меня есть шаблонный класс с перегруженным + оператор. Это хорошо работает, когда я добавляю, что два 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;
14
задан SoapBox 13 June 2010 в 01:12
поделиться

7 ответов

Стивен уже дал хорошее объяснение проблем, с которыми вы можете столкнуться при этом. Вы можете определить перегрузки для всех возможных комбинаций всех экземпляров шаблона (таким образом, у вас фактически будут операторы, определенные для 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; вы бы хотели проконсультироваться с документацией любого используемого вами компилятора.

13
ответ дан 1 December 2019 в 06:59
поделиться

Получите компилятор, поддерживающий новый оператор decltype C ++ 0x.

template < typename T1, typename T2 >
auto add(T1 t1, T2 t2) -> decltype(t1+t2)
{
  return t1 + t2;
}

Теперь вам не нужно возиться с этими "чертами" классов.

2
ответ дан 1 December 2019 в 06:59
поделиться

Оператор сложения обычно должен быть бесплатной функцией, чтобы не отдавать предпочтение любому типу операнда, как красиво объясняет @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 .

3
ответ дан 1 December 2019 в 06:59
поделиться

Если это в основном для базовых типов, вы можете помочь себе метафункцией, пока не появится новый стандарт. Что-то вроде

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 в целочисленных типах.

2
ответ дан 1 December 2019 в 06:59
поделиться

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. Запутанные библиотеки делают пользователей грустными. Как лучше с этим бороться? Не знаю...

13
ответ дан 1 December 2019 в 06:59
поделиться

Я хочу также иметь возможность делать вот это

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.: Исправления внесены в соответствии с комментариями ниже.

5
ответ дан 1 December 2019 в 06:59
поделиться

Технически это возможно, если определить неявный case для TemplateTest:

operator TemplateTest<double>() {
    return TemplateTest<double>((double)x);
}

Практически это, вероятно, не очень хорошая идея, так как x не обязательно может быть безопасно приведено к double; так получилось, что вы используете TemplateTest здесь, но вы могли бы использовать TemplateTest позже. Возможно, вам следует переосмыслить то, что вы делаете, и решить, уверены ли вы, что вам действительно нужно такое поведение

.
0
ответ дан 1 December 2019 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: