Можно ли создать шаблонную функцию, которая проверяет, может ли примитивный тип данных соответствовать значению потенциально различного примитивного типа данных? Давайте ограничим область действия целочисленными типами на данный момент.
Точнее: возможно ли создание шаблонных функций «один подходит всем», но без получения предупреждений компилятора (логическое выражение всегда 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
Проверьте свой код Вот или же Вот.
Проверьте сгенерированную сборку Вот.
Этот вопрос был вдохновлен эта почта
Использование 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 было написано, но, видимо, есть некоторые проблемы. Увидеть Вот а также Вот.
Используя функции 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.
Конечно
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
,
Вот доказательство того, что оно проходит тесты, которые вы публикуете в вопросе..
Я использовал нечто подобное в прошлом, чтобы определить, 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)));
}
Это должно работать по крайней мере для всех арифметических типов и для пользовательских типов с соответствующими преобразованиями. (тест в идеоне).
Я предлагаю решение, используя 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 :)).
Наиболее явный способ, вероятно, заключается в использовании 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 и т. Д.
Основная идея заключается в том, что функция может быть определена вместе с пользовательским типом.