JohannesWilde
JohannesWilde

Reputation: 174

Is there a more elegent way to implement overloads for extended integer types with hidden templates in C++?

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

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

Answers (0)

Related Questions