Проблема шаблона C ++ при добавлении двух типов данных - PullRequest
14 голосов
/ 13 июня 2010

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

Ответы [ 9 ]

13 голосов
/ 13 июня 2010

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

Это обеспечит обычные арифметические преобразования (целочисленное продвижение, преобразование в число с плавающей запятой и т. д.)выполнено, и вы получите ожидаемый тип результата.

Ваша реализация C ++ может уже поддерживать эти функции C ++ 0x;Вы можете обратиться к документации любого компилятора, который вы используете.

13 голосов
/ 13 июня 2010

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

5 голосов
/ 13 июня 2010

Я тоже хочу это сделать

std :: cout << doubleTt1 + intTt2 << "\ n"; </em>

В этом случае вам, вероятно, понадобятся черты типа .В основном это шаблоны классов, содержащие 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, и благодаря предоставленным вами частично специализированным версиям вы можете убедиться, чтобудут применяться определенные типы «комбинаций».


PS: Та же техника, например, будет полезна, когда вы хотите вычесть числа без знака.Одно unsigned int, вычтенное из другого, может привести к отрицательному ответу;тип результата должен быть (signed) int.


PPS: Исправления, сделанные в соответствии с комментариями ниже.

3 голосов
/ 13 июня 2010

Оператор добавления, как правило, должен быть свободной функцией, чтобы избежать предпочтения любого типа операнда, как хорошо объясняет @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 в C ++ 0x.Вы также можете смоделировать это, используя правила оператора ?:, которые в основном довольно интуитивно понятны. promo <> - это шаблон, который использует эту технику

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 типам.

2 голосов
/ 13 июня 2010

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

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

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

2 голосов
/ 13 июня 2010

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

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.

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

1 голос
/ 12 мая 2015

Более новый ответ на старый вопрос.Для C ++ 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;
}

, в основном, возвращаетзначение, к которому будут способствовать различные арифметические операции.Одна хорошая вещь об этом - то, что это может взять любое количество шаблонных аргументов.Примечание: не забудьте добавить ::type в конце.Это то, что дает реальный результат типа, который вы хотите.

Для тех, кто работает до c ++ 11, есть улучшенная версия common_type, а также

1 голос
/ 10 ноября 2014

Вы можете добавить значения типа int и double с помощью шаблонов. В функции укажите 2 аргумента, а при передаче значений в функции укажите ее типы в угловых скобках.

пример:

//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;
}
0 голосов
/ 13 июня 2010

Технически это возможно путем определения неявного регистра в TemplateTest<double>:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...