Проверка функции, если целочисленный тип может соответствовать значению, возможно, другого (целочисленного) типа

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

Точнее: возможно ли создать шаблонные функции "one fit all", но без получения предупреждений компилятора (логическое выражение всегда истинно/ложно, сравнение с подписью/без знака, неиспользуемая переменная) и без отключения предупреждений о компиляторе? Функции также должны ограничивать как можно больше проверок во время выполнения (все тривиальные случаи следует исключать во время компиляции). Если возможно, я бы предпочел избегать использования расширений из С++ 11 и т.п. (если не существует "быстрой" замены для "старого" С++).

Примечание: "значение" не известно во время компиляции, только его тип.

Пример ожидаемого поведения:

int main(int argc, char** argv) {
    for (int i = 1; i < argc; i++) {
        const int value = atoi(argv[i]);
        std::cout << value << ": ";
        std::cout << CanTypeFitValue<int8_t>(value) << " ";
        std::cout << CanTypeFitValue<uint8_t>(value) << " ";
        std::cout << CanTypeFitValue<int16_t>(value) << " ";
        std::cout << CanTypeFitValue<uint16_t>(value) << " ";
        std::cout << CanTypeFitValue<int32_t>(value) << " ";
        std::cout << CanTypeFitValue<uint32_t>(value) << " ";
        std::cout << CanTypeFitValue<int64_t>(value) << " ";
        std::cout << CanTypeFitValue<uint64_t>(value) << std::endl;
        }

}



./a.out 6 1203032847 2394857 -13423 9324 -192992929

6: 1 1 1 1 1 1 1 1

1203032847: 0 0 0 0 1 1 1 1

2394857: 0 0 0 0 1 1 1 1

-13423: 0 0 1 0 1 0 1 0

9324: 0 0 1 1 1 1 1 1

-192992929: 0 0 0 0 1 0 1 0

Проверьте свой код здесь или здесь.

Проверьте сборку, сгенерированную здесь.

Этот вопрос был вдохновлен этим сообщением

Ответ 1

Использование numeric_limits и типов, определенных в stdint.h

Более компактный, чем мое первое решение, с той же эффективностью.

Недостаток: добавлен один дополнительный заголовок.

#include <limits>
#include <stdint.h>

using std::numeric_limits;

template <typename T, typename U>
    bool CanTypeFitValue(const U value) {
        const intmax_t botT = intmax_t(numeric_limits<T>::min() );
        const intmax_t botU = intmax_t(numeric_limits<U>::min() );
        const uintmax_t topT = uintmax_t(numeric_limits<T>::max() );
        const uintmax_t topU = uintmax_t(numeric_limits<U>::max() );
        return !( (botT > botU && value < static_cast<U> (botT)) || (topT < topU && value > static_cast<U> (topT)) );        
    }

Сгенерированный код сборки (вы можете изменить типы T и U)

Проверка правильности


Примечание: была написана версия constexpr, но, видимо, у нее есть некоторые проблемы. См. здесь и здесь.

Ответ 2

Конечно,

template <typename T, typename U>
constexpr bool CanTypeFitValue(const U value)
{return ((value>U(0))==(T(value)>T(0))) && U(T(value))==value;}

//      (         part1         ) && (      part2      )

В принципе, это имеет две части. Первая часть подтверждает, что если происходит смена знака (отбрасывание unsigned до signed или наоборот, что информация знака не потеряна. Вторая часть просто проверяет, отличен ли value от T и назад, что он сохраняет это значение, и никакие бит не были потеряны.

FYI Я не уверен, что этого достаточно, чтобы определить, поддерживается ли значение, но не может сразу думать о случае с примитивами, которые потерпят неудачу. Как мой ответ, так и ответ Кейси должны работать с определенными пользователем типами типа, если они обеспечивают оба оператора преобразования между T и U.

Здесь доказательство того, что оно проходит тесты, которые вы публикуете в вопросе.

Ответ 3

Я использовал что-то подобное в прошлом, чтобы определить, может ли T точно представлять значение u типа u (удалить constexpr, чтобы сделать этот С++ 03):

template <typename T, typename U>
constexpr inline bool CanTypeRepresentValue(const U value) {
    return ((value > U()) == (static_cast<T>(value) > T())) &&
           (value == static_cast<U>(static_cast<T>(value)));
}

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

Ответ 4

Использование функций C++11 (да, я знаю, что вы не просили об этом, но это все равно) и использование шаблонов, вот что я придумал:

http://ideone.com/lBxnAW (обновленная версия: теперь также принимает неподписанные, подписанные, короткие и красивые)

Это в основном использует std::enable_if экстенсивно с type_traits std::is_unsigned и std::is_integral. Лучше всего читать снизу вверх (по мере того, как дерево решений собирается оттуда).

Очевидно, это почти все время компиляции, поэтому сборка должна быть довольно маленькой.

Это решение может обрабатывать целые и плавающие целевые типы, а также интегральные и плавающие исходные типы.

Если проверка не является тривиальной (т.е. границы типа данных должны быть проверены), значение actual_type n ставится на typename std::common_type<target, actual_type>::type статически.

Каждое решение is_integral и is_unsigned и is_same выполняется во время компиляции, поэтому никаких накладных расходов на это во время выполнения. Проверка сводится к некоторым lower_bound(target) <= value и/или value <= upper_bound(target) после того, как типы будут сбрасываться на общий тип (чтобы избежать предупреждений и предотвращения переполнения).

#include <cmath> // necessary to check for floats too
#include <cstdint> // for testing only
#include <iomanip> // for testing only
#include <iostream> // for testing only
#include <limits> // necessary to check ranges
#include <type_traits> // necessary to check type properties (very efficient, compile time!)

// the upper bound must always be checked
template <typename target_type, typename actual_type>
bool test_upper_bound(const actual_type n)
{
   typedef typename std::common_type<target_type, actual_type>::type common_type;
   const auto c_n = static_cast<common_type>(n);
   const auto t_max = static_cast<common_type>(std::numeric_limits<target_type>::max());
   return ( c_n <= t_max );
}

// the lower bound is only needed to be checked explicitely in non-trivial cases, see the next to functions
template <typename target_type, typename actual_type>
typename std::enable_if<!(std::is_unsigned<target_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
   typedef typename std::common_type<target_type, actual_type>::type common_type;
   const auto c_n = static_cast<common_type>(n);
   const auto t_min = static_cast<common_type>(std::numeric_limits<target_type>::lowest());
   return ( c_n >= t_min );
}

// for unsigned target types, the sign of n musn't be negative
// but that not an issue with unsigned actual_type
template <typename target_type, typename actual_type>
typename std::enable_if<std::is_integral<target_type>::value &&
                        std::is_unsigned<target_type>::value &&
                        std::is_integral<actual_type>::value &&
                        std::is_unsigned<actual_type>::value, bool>::type
test_lower_bound(const actual_type)
{
   return true;
}

// for unsigned target types, the sign of n musn't be negative
template <typename target_type, typename actual_type>
typename std::enable_if<std::is_integral<target_type>::value &&
                        std::is_unsigned<target_type>::value &&
                        (!std::is_integral<actual_type>::value ||
                         !std::is_unsigned<actual_type>::value), bool>::type
test_lower_bound(const actual_type n)
{
   return ( n >= 0 );
}

// value may be integral if the target type is non-integral
template <typename target_type, typename actual_type>
typename std::enable_if<!std::is_integral<target_type>::value, bool>::type
test_integrality(const actual_type)
{
   return true;
}

// value must be integral if the target type is integral
template <typename target_type, typename actual_type>
typename std::enable_if<std::is_integral<target_type>::value, bool>::type
test_integrality(const actual_type n)
{
   return ( (std::abs(n - std::floor(n)) < 1e-8) || (std::abs(n - std::ceil(n)) < 1e-8) );
}

// perform check only if non-trivial
template <typename target_type, typename actual_type>
typename std::enable_if<!std::is_same<target_type, actual_type>::value, bool>::type
CanTypeFitValue(const actual_type n)
{
   return test_upper_bound<target_type>(n) &&
          test_lower_bound<target_type>(n) &&
          test_integrality<target_type>(n);
}


// trivial case: actual_type == target_type
template <typename actual_type>
bool CanTypeFitValue(const actual_type)
{
   return true;
}

int main()
{
   int ns[] = {6, 1203032847, 2394857, -13423, 9324, -192992929};
   for ( const auto n : ns )
   {
      std::cout << std::setw(10) << n << "\t";
      std::cout << " " << CanTypeFitValue<int8_t>(n);
      std::cout << " " << CanTypeFitValue<uint8_t>(n);
      std::cout << " " << CanTypeFitValue<int16_t>(n);
      std::cout << " " << CanTypeFitValue<uint16_t>(n);
      std::cout << " " << CanTypeFitValue<int32_t>(n);
      std::cout << " " << CanTypeFitValue<uint32_t>(n);
      std::cout << " " << CanTypeFitValue<int64_t>(n);
      std::cout << " " << CanTypeFitValue<uint64_t>(n);
      std::cout << " " << CanTypeFitValue<float>(n);
      std::cout << " " << CanTypeFitValue<double>(n);
      std::cout << "\n";
   }
   std::cout << "\n";
   unsigned long long uss[] = {6, 1201146189143ull, 2397, 23};
   for ( const auto n : uss )
   {
      std::cout << std::setw(10) << n << "\t";
      std::cout << " " << CanTypeFitValue<int8_t>(n);
      std::cout << " " << CanTypeFitValue<uint8_t>(n);
      std::cout << " " << CanTypeFitValue<int16_t>(n);
      std::cout << " " << CanTypeFitValue<uint16_t>(n);
      std::cout << " " << CanTypeFitValue<int32_t>(n);
      std::cout << " " << CanTypeFitValue<uint32_t>(n);
      std::cout << " " << CanTypeFitValue<int64_t>(n);
      std::cout << " " << CanTypeFitValue<uint64_t>(n);
      std::cout << " " << CanTypeFitValue<float>(n);
      std::cout << " " << CanTypeFitValue<double>(n);
      std::cout << "\n";
   }
   std::cout << "\n";
   float fs[] = {0.0, 0.5, -0.5, 1.0, -1.0, 1e10, -1e10};
   for ( const auto f : fs )
   {
      std::cout << std::setw(10) << f << "\t";
      std::cout << " " << CanTypeFitValue<int8_t>(f);
      std::cout << " " << CanTypeFitValue<uint8_t>(f);
      std::cout << " " << CanTypeFitValue<int16_t>(f);
      std::cout << " " << CanTypeFitValue<uint16_t>(f);
      std::cout << " " << CanTypeFitValue<int32_t>(f);
      std::cout << " " << CanTypeFitValue<uint32_t>(f);
      std::cout << " " << CanTypeFitValue<int64_t>(f);
      std::cout << " " << CanTypeFitValue<uint64_t>(f);
      std::cout << " " << CanTypeFitValue<float>(f);
      std::cout << " " << CanTypeFitValue<double>(f);
      std::cout << "\n";
   }
}

Эта (новая) версия быстро решает (во время компиляции!), если необходимы проверки (относительно верхней границы, нижней границы и целостности) и использует правильную версию (чтобы избежать предупреждений о глупом >= 0 сравнении с неподписанными типами) также во время компиляции). Например. интеграция не требуется проверять, если цель является float, нижняя граница не нуждается в проверке, если оба типа без знака и т.д.

Наиболее очевидная оптимизация (имеющая равные типы) выполняется с помощью std::is_same.

Этот подход также может быть расширен до используемых типов со специализированными шаблонами. Такие проверки, как std::is_integral, будут отрицательными для этих типов.

Вы можете проверить, что выход ассемблера довольно мал (за исключением очевидного случая поплавков) здесь или путем вызова g++ с -S.

Ответ 5

Я предлагаю решение, используя numeric_limits

#include <limits>
using std::numeric_limits;

template <typename T, typename U>
    bool CanTypeFitValue(const U value) {
        if (numeric_limits<T>::is_signed == numeric_limits<U>::is_signed) {
            if (numeric_limits<T>::digits >= numeric_limits<U>::digits)
                return true;
            else
                return (static_cast<U>(numeric_limits<T>::min() ) <= value && static_cast<U>(numeric_limits<T>::max() ) >= value);
        }
        else {
            if (numeric_limits<T>::is_signed) {
                if (numeric_limits<T>::digits > numeric_limits<U>::digits) //Not >= in this case!
                    return true;
                else
                    return (static_cast<U>(numeric_limits<T>::max() ) >= value);
            }
            else ///U is signed, T is not
                if (value < static_cast<U> (0) )
                    return false;
                else
                    if (numeric_limits<T>::digits >= numeric_limits<U>::digits)
                        return true;
                    else
                        return (static_cast<U>(numeric_limits<T>::max() ) >= value);
        }
    }

Протестировано здесь (Извините за использование atoi:)).

Ответ 6

Наиболее явным способом является использование SFINAE и функции для каждого типа. Что-то вроде этого:

#include <limits>


template <typename T>
bool CanTypeFitValue(int) {
    return false;
}

template <typename T>
bool CanSignedNumericTypeFitValue(int value) {
    return (value >= std::numeric_limits<T>::min() && 
            value <= std::numeric_limits<T>::max());
}

template <typename T>
bool CanUnsignedNumericTypeFitValue(int value) {
    return (value >= 0 && 
            static_cast<unsigned>(value) <= std::numeric_limits<T>::max());
}

template <> bool CanTypeFitValue<int8_t>(int value) { 
    return CanSignedNumericTypeFitValue<int8_t>(value); 
}
template <> bool CanTypeFitValue<uint8_t>(int value) {
    return CanUnsignedNumericTypeFitValue<uint8_t>(value); 
}
// .....
//template <> bool CanTypeFitValue<SomeUserClass * > { 
//    return impl_details(value);
//};

Он также обычно используется в STL/Boost и т.д.

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