Reputation: 174
As this question already explored, extended integer type are not caught by functions that are not explicitly overloaded for them.
Explicitely overloading for the respective types however can pose problems when used with different compilers using different extended integer types [see here].
This seems like an XY problem, though. Your main issue is that you want to be able to overload for the fixed-width types on platforms where they're distinct from the fundamental types, but don't know how to do this without breaking your code on platforms where they're the same as the fundamental types.
This proved problematic for me on Windows 11 [MSVC, MinGW], Ubuntu 24 [g++] and Petalinux - where
long
was an extended type on Windows and Petalinux andlong long
was an extended type on Ubuntu.Thus it was not possible to distinguish this using - admittedly unrelated - preprocessor defines #ifdef WIN32
and #ifdef UNIX
.
One additional requirement was to not make the functions with the required overloads for extended integral types visible in a header file - as the actual implementation is quite heavy and therefore preferredly hidden in a cpp-file.
My current solution relies heavily on std::enable_if
, SFINAE and a lot of circumstantial template code. What fallows is a somewhat contrived, somewhat minimal example.
templateHelpers.hpp
#ifndef TEMPLATEHELPERS_HPP
#define TEMPLATEHELPERS_HPP
#include <stddef.h>
#include <stdint.h>
#include <type_traits>
namespace Custom
{
template <size_t size>
struct UnsignedFixedSized
{
private:
UnsignedFixedSized() = delete;
};
template <>
struct UnsignedFixedSized<1>
{
typedef uint8_t type;
};
template <>
struct UnsignedFixedSized<2>
{
typedef uint16_t type;
};
template <>
struct UnsignedFixedSized<4>
{
typedef uint32_t type;
};
template <>
struct UnsignedFixedSized<8>
{
typedef uint64_t type;
};
// -----
template <class T, std::enable_if_t<std::is_integral_v<T>> const * = nullptr>
struct UnsignedFixedSameSize
{
typedef typename UnsignedFixedSized<sizeof(T)>::type type;
private:
UnsignedFixedSameSize() = delete;
};
template <size_t size>
struct SignedFixedSized
{
private:
SignedFixedSized() = delete;
};
template <>
struct SignedFixedSized<1>
{
typedef int8_t type;
};
template <>
struct SignedFixedSized<2>
{
typedef int16_t type;
};
template <>
struct SignedFixedSized<4>
{
typedef int32_t type;
};
template <>
struct SignedFixedSized<8>
{
typedef int64_t type;
};
// -----
template <class T, std::enable_if_t<std::is_integral_v<T>> const * = nullptr>
struct SignedFixedSameSize
{
typedef typename SignedFixedSized<sizeof(T)>::type type;
private:
SignedFixedSameSize() = delete;
};
// -----
template <class T, bool signedType>
struct IntegralFixedSameSize_
{
private:
IntegralFixedSameSize_() = delete;
};
template <class T>
struct IntegralFixedSameSize_<T, true>
{
typedef typename SignedFixedSameSize<T>::type type;
};
template <class T>
struct IntegralFixedSameSize_<T, false>
{
typedef typename UnsignedFixedSameSize<T>::type type;
};
template <class T, std::enable_if_t<std::is_integral_v<T>> const * = nullptr>
struct IntegralFixedSameSize
{
typedef typename IntegralFixedSameSize_<T, std::is_signed_v<T>>::type type;
private:
IntegralFixedSameSize() = delete;
};
// -----
template <class T, std::enable_if_t<std::is_integral_v<T>> const * = nullptr>
struct DistinctIntegralNonFixedSizeType
{
static bool constexpr value = !std::is_same_v<typename IntegralFixedSameSize<T>::type, T>;
private:
DistinctIntegralNonFixedSizeType() = delete;
};
// -----
template <class T, bool integral>
struct DistinctArithmeticNonFixedSizeOrFloatType_
{
private:
DistinctArithmeticNonFixedSizeOrFloatType_() = delete;
};
template <class T>
struct DistinctArithmeticNonFixedSizeOrFloatType_<T, true>
{
static bool constexpr value = DistinctIntegralNonFixedSizeType<T>::value;
};
template <class T>
struct DistinctArithmeticNonFixedSizeOrFloatType_<T, false>
{
static bool constexpr value = false;
};
template <class T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
struct DistinctArithmeticNonFixedSizeOrFloatType
{
static bool constexpr value = DistinctArithmeticNonFixedSizeOrFloatType_<T, std::is_integral_v<T>>::value;
private:
DistinctArithmeticNonFixedSizeOrFloatType() = delete;
};
// -----
} // namespace Custom
#endif // TEMPLATEHELPERS_HPP
stringtonumber.hpp
#ifndef STRINGTONUMBER_HPP
#define STRINGTONUMBER_HPP
#include "templateHelpers.hpp"
#include <string>
#include <type_traits>
namespace Custom
{
template <class T, std::enable_if_t<!Custom::DistinctArithmeticNonFixedSizeOrFloatType<T>::value> const * = nullptr>
T stringToNumber(std::string const & text);
template <class T, std::enable_if_t<Custom::DistinctArithmeticNonFixedSizeOrFloatType<T>::value> const * = nullptr>
T stringToNumber(std::string const & text)
{
return stringToNumber<typename Custom::IntegralFixedSameSize<T>::type>(text);
}
} // namespace Custom
#endif // STRINGTONUMBER_HPP
stringtonumber.cpp
#include "stringtonumber.hpp"
#include <cstdint>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <type_traits>
namespace // anonymous
{
template <class T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
T stringToNumber_(std::string const & text)
{
std::istringstream stream(text);
std::remove_cv_t<T> value = 0;
stream >> value;
if (!stream.eof())
{
throw std::runtime_error(std::string("Failed to decode \"") + text + "\".");
}
return value;
}
} // namespace anonymous
namespace Custom
{
template <>
uint8_t stringToNumber<uint8_t>(std::string const & text)
{
uint16_t const value = stringToNumber_<uint16_t>(text);
if (std::numeric_limits<uint8_t>::max() < value)
{
throw std::runtime_error(std::string("Failed to decode \"") + text + "\".");
}
return static_cast<uint8_t>(value);
}
template <>
uint16_t stringToNumber<uint16_t>(std::string const & text)
{
return stringToNumber_<uint16_t>(text);
}
template <>
uint32_t stringToNumber<uint32_t>(std::string const & text)
{
return stringToNumber_<uint32_t>(text);
}
template <>
uint64_t stringToNumber<uint64_t>(std::string const & text)
{
return stringToNumber_<uint64_t>(text);
}
template <>
int8_t stringToNumber<int8_t>(std::string const & text)
{
int16_t const value = stringToNumber_<int16_t>(text);
if ((std::numeric_limits<uint8_t>::max() < value) || (std::numeric_limits<int8_t>::lowest() > value))
{
throw std::runtime_error(std::string("Failed to decode \"") + text + "\".");
}
return static_cast<int8_t>(value);
}
template <>
int16_t stringToNumber<int16_t>(std::string const & text)
{
return stringToNumber_<int16_t>(text);
}
template <>
int32_t stringToNumber<int32_t>(std::string const & text)
{
return stringToNumber_<int32_t>(text);
}
template <>
int64_t stringToNumber<int64_t>(std::string const & text)
{
return stringToNumber_<int64_t>(text);
}
template <>
float stringToNumber<float>(std::string const & text)
{
return stringToNumber_<float>(text);
}
template <>
double stringToNumber<double>(std::string const & text)
{
return stringToNumber_<double>(text);
}
template <>
long double stringToNumber<long double>(std::string const & text)
{
return stringToNumber_<long double>(text);
}
} // namespace Custom
main.cpp
#include <iostream>
#include "stringtonumber.hpp"
int main()
{
std::cout << " uint8_t: " << static_cast<unsigned>(Custom::stringToNumber<uint8_t>("123")) << std::endl;
std::cout << "uint16_t: " << Custom::stringToNumber<uint16_t>("16003") << std::endl;
std::cout << "uint32_t: " << Custom::stringToNumber<uint32_t>("4576924") << std::endl;
std::cout << "uint64_t: " << Custom::stringToNumber<uint64_t>("5000000000") << std::endl;
std::cout << " u: " << Custom::stringToNumber<unsigned>("4576924") << std::endl;
std::cout << " ul: " << Custom::stringToNumber<unsigned long>("4576924") << std::endl;
std::cout << " ull: " << Custom::stringToNumber<unsigned long long>("5000000000") << std::endl;
std::cout << std::endl;
std::cout << " int8_t: " << static_cast<unsigned>(Custom::stringToNumber<int8_t>("-123")) << std::endl;
std::cout << " int16_t: " << Custom::stringToNumber<int16_t>("-7543") << std::endl;
std::cout << " int32_t: " << Custom::stringToNumber<int32_t>("-2176924") << std::endl;
std::cout << " int64_t: " << Custom::stringToNumber<int64_t>("-5000000000") << std::endl;
std::cout << " i: " << Custom::stringToNumber<int>("-2176924") << std::endl;
std::cout << " l: " << Custom::stringToNumber<long>("-2176924") << std::endl;
std::cout << " ll: " << Custom::stringToNumber<long long>("-5000000000") << std::endl;
std::cout << std::endl;
std::cout << " float: " << Custom::stringToNumber<float>("2.345e6") << std::endl;
std::cout << " double: " << Custom::stringToNumber<double>("3.14e17") << std::endl;
std::cout << "l double: " << Custom::stringToNumber<long double>("-3.29") << std::endl;
return 0;
}
Is there a more elegant way to implement overloads for extended integer types in a multi-compiler compatible way without having to publish the full template-method implementation in the header file?
Upvotes: 0
Views: 86