Следовательно, нам, возможно, понадобится некоторый метод шаблона метапрограммирования для получения возвращаемого типа до тех пор, пока T является int.
Не все так просто.
Если T
равно int
, вы не уверены, что long
достаточно.
Стандарт говорит только, что
1) количество битов для int
(sizeof(int) * CHAR_BIT
) равно не менее 16
2) число битов для long
(sizeof(long) * CHAR_BIT
) составляет не менее 32
3) sizeof(int) <= sizeof(long)
Так что, если компилятор управляет int
с sizeof(int) == sizeof(long)
, это совершенно законно и
adder<long>(INT_MAX-10, INT_MAX-3);
не работает, потому что long
может быть недостаточно, чтобы содержать (без переполнения) сумму между двумя int
.
Я не вижу простого и элегантного решения.
Лучшее, что приходит мне в голову, основано на том факте, что C ++ 11 ввел следующие типы
1) std::int_least8_t
, наименьший целочисленный тип с не менее 8 битами
2) std::int_least16_t
, наименьший целочисленный тип по крайней мере с 16 битами
3) std::int_least32_t
, наименьший целочисленный тип не менее 32 бит
4) std::int_least64_t
, наименьший целочисленный тип по крайней мере с 64 битами
C ++ 11 также вводит std::intmax_t
в качестве целочисленного типа максимальной ширины.
Поэтому я предлагаю следующий шаблонный селектор типа
template <std::size_t N, typename = std::true_type>
struct typeFor;
/* in case std::intmax_t is bigger than 64 bits */
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool,
(N > 64u) && (N <= sizeof(std::intmax_t)*CHAR_BIT)>>
{ using type = std::intmax_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 32u) && (N <= 64u)>>
{ using type = std::int_least64_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 16u) && (N <= 32u)>>
{ using type = std::int_least32_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 8u) && (N <= 16u)>>
{ using type = std::int_least16_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N <= 8u)>>
{ using type = std::int_least8_t; };
что при заданном количестве битов определяет соответствующий наименьший целочисленный тип "по крайней мере".
Предлагаю также следующее using
template <typename T>
using typeNext = typename typeFor<1u+sizeof(T)*CHAR_BIT>::type;
что, учитывая тип T
, обнаруживает наименьший целочисленный тип, который обязательно содержит сумму между двумя T
значениями (целое число с количеством битов, которое по крайней мере равно числу битов T
плюс один ).
Так что adder()
просто станет
template<typename T>
typeNext<T> adder (T const & i1, T const & i2)
{ return {typeNext<T>{i1} + i2}; }
Обратите внимание, что возвращаемое значение не просто
return i1 + i2;
в противном случае вы возвращаете правильный тип, но с неверным значением: i1 + i2
вычисляется как значение T
, так что вы можете иметь переполнение, тогда сумма присваивается переменной typeNext<T>
.
Чтобы избежать этой проблемы, вы должны инициализировать временную переменную typeNext<T>
одним из двух значений (typeNext<T>{i1}
), затем добавить другое (typeNext<T>{i1} + i2
), получающее значение typeNext<T>
, и, наконец, вернуть вычисленное значение , Таким образом, сумма рассчитывается как сумма typeNext<T>
, и вы не переполняетесь.
Ниже приведен полный пример компиляции
#include <cstdint>
#include <climits>
#include <iostream>
#include <type_traits>
template <std::size_t N, typename = std::true_type>
struct typeFor;
/* in case std::intmax_t is bigger than 64 bits */
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool,
(N > 64u) && (N <= sizeof(std::intmax_t)*CHAR_BIT)>>
{ using type = std::intmax_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 32u) && (N <= 64u)>>
{ using type = std::int_least64_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 16u) && (N <= 32u)>>
{ using type = std::int_least32_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N > 8u) && (N <= 16u)>>
{ using type = std::int_least16_t; };
template <std::size_t N>
struct typeFor<N, std::integral_constant<bool, (N <= 8u)>>
{ using type = std::int_least8_t; };
template <typename T>
using typeNext = typename typeFor<1u+sizeof(T)*CHAR_BIT>::type;
template<typename T>
typeNext<T> adder (T const & i1, T const & i2)
{ return {typeNext<T>{i1} + i2}; }
int main()
{
auto x = adder(INT_MAX-10, INT_MAX-3);
std::cout << "int: " << sizeof(int)*CHAR_BIT << std::endl;
std::cout << "long: " << sizeof(long)*CHAR_BIT << std::endl;
std::cout << "x: " << sizeof(x)*CHAR_BIT << std::endl;
std::cout << std::is_same<long, decltype(x)>::value << std::endl;
}
На моей 64-битной платформе Linux я получаю 32-битную для int
, 64-битную для long
и x
, а также что long
и decltype(x)
относятся к одному типу.
Но это верно для моей платформы; ничто не гарантирует, что long
и decltype(x)
всегда одинаковы.
Заметьте также, что попытка получить тип на сумму двух std::intmax_t
*
std::intmax_t y {};
auto z = adder(y, y);
выдает ошибку и не компилируется, потому что не определено typeFor
для N
больше, чем sizeof(std::intmax_t)*CHAR_BIT
.