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

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

Точнее: возможно ли создание шаблонных функций «один подходит всем», но без получения предупреждений компилятора (логическое выражение всегда true / false, сравнение со знаком / без знака, неиспользуемая переменная) и без отключения проверки предупреждений компилятора? Функции также должны максимально ограничивать проверки во время выполнения (все тривиальные случаи должны быть исключены во время компиляции). Если возможно, я бы предпочел избегать использования расширений из C ++ 11 и тому подобного (если не существует «быстрой» замены «старого» C ++).

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

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

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

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

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

Этот вопрос был вдохновлен эта почта

14

Решение

Использование 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 было написано, но, видимо, есть некоторые проблемы. Увидеть Вот а также Вот.

8

Другие решения

Используя функции 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's 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 сравнениях с неподписанными типами) (также при компилировать время). Например. нет необходимости проверять целостность, если цель является плавающей, нижнюю границу не нужно проверять, если оба типа не подписаны и т. д.

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

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

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

6

Конечно

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 и обратно, что он сохраняет свою ценность, и ни один бит не был потерян.

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

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

5

Я использовал нечто подобное в прошлом, чтобы определить, T может представлять ценность u типа U точно (удалить constexpr сделать это C ++ 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)));
}

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

3

Я предлагаю решение, используя 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 :)).

1

Наиболее явный способ, вероятно, заключается в использовании 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 и т. Д.

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

-1
По вопросам рекламы [email protected]