вывод типа возврата на основе максимального диапазона ввода возможного в C ++ - PullRequest
0 голосов
/ 18 ноября 2018

Мне недавно задали этот вопрос в одном из интервью C ++, где меня попросили улучшить приведенный ниже фрагмент кода, который завершается неудачно при добавлении двух результатов int, в результате которых получается long, и тип возвращаемого значения должен быть соответственнопроизводный.

Здесь приведенный ниже код завершается ошибкой, потому что вывод на основе decltype() не достаточно интеллектуален, чтобы идентифицировать его на основе фактического диапазона значений ввода, но тип и получает тип возвращаемого значения таким же.Следовательно, нам, возможно, понадобится некоторый метод шаблона метапрограммирования для получения возвращаемого типа как long, если T равно int.

Как можно обобщить любые подсказки или подсказки?

Я чувствую, что decltype() здесь не поможет.

#include<iostream>
#include<string>
#include<climits>

using namespace std;

template<typename T> auto adder(const T& i1, const T& i2) -> decltype(i1+i2)
{  
  return(i1+i2);
}

int main(int argc, char* argv[])
{
  cout << adder(INT_MAX-10, INT_MAX-3) << endl; // wrong.
  cout << adder<long>(INT_MAX-10, INT_MAX-3) << endl; // correct!!.
  return(0);   
}

1 Ответ

0 голосов
/ 18 ноября 2018

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

...