What is the best way to determine a common numeric type in a template parameter pack with:
- the smallest size,
- no loss of precision, and
- no risk of overflow/underflow when converting any type in the parameter pack to this "ideal" common type?
The variadic template (best_common_numeric_type
) could be used like so:
template<typename... NumericTypes>
auto some_numeric_func(const NumericTypes&...)
-> typename best_common_numeric_type<NumericTypes...>::type;
And have instantiations like the following:
[1] best_common_numeric_type<long, unsigned long, float, double, int>::type = double
[2] best_common_numeric_type<unsigned int, unsigned long>::type = unsigned long
[3] best_common_numeric_type<signed int, signed long>::type = signed long
[4] best_common_numeric_type<signed int, unsigned int>::type = signed long
[5] best_common_numeric_type<signed int, unsigned long>::type = int128_t (maybe)
So in case [4] for example, ::type
would have to be signed long
, since signed int
could not hold an unsigned int
without risk of overflow, and conversely unsigned int
could not hold a signed int
without risk of underflow.
The same applies in [5], except now a signed long
is no longer sufficient since it could not hold the unsigned long
without risk of overflow.
(The implementation might be data model specific, but you get the idea.)
So what might be the best way in C++11 to achieve this?
I am a little bit late to the party, here is my solution without Boost:
#include <type_traits>
#include <cstdint>
template<class I, bool Signed> struct mk_signed { typedef I type; };
template<> struct mk_signed<uint8_t , true> { typedef int16_t type; };
template<> struct mk_signed<uint16_t, true> { typedef int32_t type; };
template<> struct mk_signed<uint32_t, true> { typedef int64_t type; };
template<> struct mk_signed<uint64_t, true> { typedef int64_t type; };
template <typename... Ts> struct best_common_numeric_type;
template <typename T> struct best_common_numeric_type<T> { typedef T type; };
template <typename T, typename... Ts>
struct best_common_numeric_type<T, Ts...> {
typedef typename best_common_numeric_type<Ts...>::type TS;
typedef typename std::conditional < (sizeof(T) > sizeof(TS)), T, TS>::type bigger_integral;
constexpr static bool fp = std::is_floating_point<T>::value || std::is_floating_point<TS>::value;
constexpr static bool have_signed = !fp && (std::is_signed<T>::value || std::is_signed<TS>::value);
typedef typename std::conditional <
fp,
typename std::common_type<T,TS>::type,
typename mk_signed<bigger_integral,have_signed>::type
>::type type;
};
You could use Boost Integer to select the proper cases.
Ignoring for a moment the cases on non-integral element types, here's a quick test of the proposed cases (GCC doesn't have int128_t
as it appears):
#include <boost/mpl/vector.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/max_element.hpp>
#include <boost/integer.hpp>
#include <limits>
using namespace boost;
namespace best_fit_
{
// wrappers around Boost Integer http://www.boost.org/doc/libs/1_54_0/libs/integer/doc/html/boost_integer/integer.html#boost_integer.integer.sized
template <bool is_signed, int bin_digits> struct select_int;
template <int bin_digits> struct select_int<true, bin_digits> {
using type = typename boost::int_t<bin_digits + 1>::least;
};
template <int bin_digits> struct select_int<false, bin_digits> {
using type = typename boost::uint_t<bin_digits>::least;
};
// query helper
struct digits {
template <typename I> using apply = mpl::int_<std::numeric_limits<I>::digits>;
};
}
template <typename... I>
struct best_common_integral
{
private:
using Ints = mpl::vector<I...>;
using Bits = typename mpl::transform<Ints, best_fit_::digits>::type;
template <typename J>
struct is_signed { static constexpr bool value = std::numeric_limits<J>::is_signed; };
using max = typename mpl::deref<typename mpl::max_element<Bits>::type>::type;
// sigh, there is no `mpl::any`, AFAICT
using sign = typename mpl::fold<
Ints,
mpl::bool_<false>,
mpl::if_<is_signed<mpl::_2>, mpl::bool_<true>, mpl::_1>
>::type;
public:
using type = typename best_fit_::select_int<sign::value, max::value>::type;
};
#include <typeinfo>
#include <iostream>
#include <cassert>
int main()
{
using case1 = best_common_integral<long, unsigned long, float, double, int>;
using case2 = best_common_integral<unsigned int, unsigned long>;
using case3 = best_common_integral<signed int, signed long>;
using case4 = best_common_integral<signed int, unsigned int>;
using case5 = best_common_integral<signed int, unsigned long>;
//assert(typeid(case1::type) == typeid(double));
assert(typeid(case2::type) == typeid(unsigned long));
assert(typeid(case3::type) == typeid(signed long));
assert(typeid(case4::type) == typeid(signed long));
//assert(typeid(case5::type) == typeid(int128_t (maybe)));
}
Note: somehow I got it stuck in my head that you needed C++03 for this. This can be simplified for C++11. This also does NOT pick the smallest size.
There's nothing standard for this to my knowledge, but it can be done: http://coliru.stacked-crooked.com/view?id=c6aa42345f91ab51d745d56573b15a04-4f34a5fd633ef9f45cb08f8e23efae0a
First the "thinker" structs.
template<bool isfloat, bool negative> struct best_numeric_type
{typedef long double type;};
template<> struct best_numeric_type<false, true>
{typedef long long type;};
template<> struct best_numeric_type<false, false>
{typedef unsigned long long type;};
Then the base cases:
template<class T> struct best_common_numeric_type1 {
static const bool isfloat=false;
static const bool negative=false;
typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<> struct best_common_numeric_type1<char> {
static const bool isfloat=false;
static const bool negative=true;
typedef typename best_numeric_type<isfloat, negative>::type type;
};//copy-paste for signed char, short, int, long, and long long.
template<> struct best_common_numeric_type1<float> {
static const bool isfloat=true;
static const bool negative=false;
typedef typename best_numeric_type<isfloat, negative>::type type;
};//copy-paste for double and long double.
Then the joiners:
template<class First, class Second>
struct best_common_numeric_type2 {
static const bool isfloat = best_common_numeric_type1<First>::isfloat | best_common_numeric_type1<Second>::isfloat;
static const bool negative = best_common_numeric_type1<First>::negative | best_common_numeric_type1<Second>::negative;
typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<class First, class Second, class Third>
struct best_common_numeric_type3 {
static const bool isfloat = best_common_numeric_type2<First, Second>::isfloat | best_common_numeric_type1<Third>::isfloat;
static const bool negative = best_common_numeric_type2<First, Second>::negative | best_common_numeric_type1<Third>::negative;
typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<class First, class Second, class Third, class Fourth>
struct best_common_numeric_type4 {
static const bool isfloat = best_common_numeric_type3<First, Second, Third>::isfloat | best_common_numeric_type1<Fourth>::isfloat;
static const bool negative = best_common_numeric_type3<First, Second, Third>::negative | best_common_numeric_type1<Fourth>::negative;
typedef typename best_numeric_type<isfloat, negative>::type type;
};
template<class First, class Second, class Third, class Fourth, class Fifth>
struct best_common_numeric_type5 {
static const bool isfloat = best_common_numeric_type4<First, Second, Third, Fourth>::isfloat | best_common_numeric_type1<Fifth>::isfloat;
static const bool negative = best_common_numeric_type4<First, Second, Third, Fourth>::negative | best_common_numeric_type1<Fifth>::negative;
typedef typename best_numeric_type<isfloat, negative>::type type;
};
And finally a test:
#include <typeinfo>
#include <iostream>
void printer(long double) {std::cout << "long double\n";}
void printer(unsigned long long) {std::cout << "ull\n";}
void printer(long long) {std::cout << "ll\n";}
void printer(...) {std::cout << "else\n";}
int main() {
printer(best_common_numeric_type5<long, unsigned long, float, double, int>::type());
printer(best_common_numeric_type2<unsigned int, unsigned long>::type());
printer(best_common_numeric_type2<signed int, signed long>::type());
printer(best_common_numeric_type2<signed int, unsigned int>::type());
printer(best_common_numeric_type2<signed int, unsigned long>::type());
printer(best_common_numeric_type2<float, char>::type());
}
Results:
long double
ull
ll
ll
ll
long double
来源:https://stackoverflow.com/questions/18283807/determining-the-optimal-common-numeric-type-in-a-template-parameter-pack