ClickHouse/dbms/include/DB/Functions/NumberTraits.h

504 lines
22 KiB
C++
Raw Normal View History

2011-08-09 15:57:33 +00:00
#pragma once
#include <boost/mpl/bool.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/or.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/greater.hpp>
2011-08-09 16:42:08 +00:00
#include <boost/mpl/min_max.hpp>
#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/comparison.hpp>
2011-08-09 15:57:33 +00:00
#include <DB/Core/Types.h>
2016-05-03 23:19:14 +00:00
#include <tuple>
2011-08-09 15:57:33 +00:00
namespace DB
{
/** Позволяет получить тип результата применения функций +, -, *, /, %, div (целочисленное деление).
* Правила отличаются от используемых в C++.
*/
namespace NumberTraits
{
2011-08-21 03:41:37 +00:00
2016-05-03 23:19:14 +00:00
typedef boost::mpl::false_ Unsigned;
typedef boost::mpl::true_ Signed;
typedef boost::mpl::false_ Integer;
typedef boost::mpl::true_ Floating;
typedef boost::mpl::int_<0> Bits0;
typedef boost::mpl::int_<8> Bits8;
typedef boost::mpl::int_<16> Bits16;
typedef boost::mpl::int_<32> Bits32;
typedef boost::mpl::int_<64> Bits64;
typedef boost::mpl::int_<1024> BitsTooMany;
struct Error {};
template <typename T> struct Next;
template <> struct Next<Bits0> { typedef Bits0 Type; };
template <> struct Next<Bits8> { typedef Bits16 Type; };
template <> struct Next<Bits16> { typedef Bits32 Type; };
template <> struct Next<Bits32> { typedef Bits64 Type; };
template <> struct Next<Bits64> { typedef Bits64 Type; };
template <typename T> struct ExactNext { typedef typename Next<T>::Type Type; };
template <> struct ExactNext<Bits64> { typedef BitsTooMany Type; };
template <typename T> struct Traits;
template <> struct Traits<void> { typedef Unsigned Sign; typedef Integer Floatness; typedef Bits0 Bits; };
template <> struct Traits<UInt8> { typedef Unsigned Sign; typedef Integer Floatness; typedef Bits8 Bits; };
template <> struct Traits<UInt16> { typedef Unsigned Sign; typedef Integer Floatness; typedef Bits16 Bits; };
template <> struct Traits<UInt32> { typedef Unsigned Sign; typedef Integer Floatness; typedef Bits32 Bits; };
template <> struct Traits<UInt64> { typedef Unsigned Sign; typedef Integer Floatness; typedef Bits64 Bits; };
template <> struct Traits<Int8> { typedef Signed Sign; typedef Integer Floatness; typedef Bits8 Bits; };
template <> struct Traits<Int16> { typedef Signed Sign; typedef Integer Floatness; typedef Bits16 Bits; };
template <> struct Traits<Int32> { typedef Signed Sign; typedef Integer Floatness; typedef Bits32 Bits; };
template <> struct Traits<Int64> { typedef Signed Sign; typedef Integer Floatness; typedef Bits64 Bits; };
template <> struct Traits<Float32> { typedef Signed Sign; typedef Floating Floatness; typedef Bits32 Bits; };
template <> struct Traits<Float64> { typedef Signed Sign; typedef Floating Floatness; typedef Bits64 Bits; };
template <typename Sign, typename Floatness, typename Bits> struct Construct;
template <> struct Construct<Unsigned, Integer, Bits0> { typedef void Type; };
template <> struct Construct<Unsigned, Floating, Bits0> { typedef void Type; };
template <> struct Construct<Signed, Integer, Bits0> { typedef void Type; };
template <> struct Construct<Signed, Floating, Bits0> { typedef void Type; };
template <> struct Construct<Unsigned, Integer, Bits8> { typedef UInt8 Type; };
template <> struct Construct<Unsigned, Integer, Bits16> { typedef UInt16 Type; };
template <> struct Construct<Unsigned, Integer, Bits32> { typedef UInt32 Type; };
template <> struct Construct<Unsigned, Integer, Bits64> { typedef UInt64 Type; };
template <> struct Construct<Unsigned, Floating, Bits8> { typedef Float32 Type; };
template <> struct Construct<Unsigned, Floating, Bits16> { typedef Float32 Type; };
template <> struct Construct<Unsigned, Floating, Bits32> { typedef Float32 Type; };
template <> struct Construct<Unsigned, Floating, Bits64> { typedef Float64 Type; };
template <> struct Construct<Signed, Integer, Bits8> { typedef Int8 Type; };
template <> struct Construct<Signed, Integer, Bits16> { typedef Int16 Type; };
template <> struct Construct<Signed, Integer, Bits32> { typedef Int32 Type; };
template <> struct Construct<Signed, Integer, Bits64> { typedef Int64 Type; };
template <> struct Construct<Signed, Floating, Bits8> { typedef Float32 Type; };
template <> struct Construct<Signed, Floating, Bits16> { typedef Float32 Type; };
template <> struct Construct<Signed, Floating, Bits32> { typedef Float32 Type; };
template <> struct Construct<Signed, Floating, Bits64> { typedef Float64 Type; };
template <typename Sign, typename Floatness> struct Construct<Sign, Floatness, BitsTooMany> { typedef Error Type; };
template <typename T>
inline bool isErrorType()
{
return false;
}
template <>
inline bool isErrorType<Error>()
{
return true;
}
/** Результат сложения или умножения вычисляется по следующим правилам:
* - если один из аргументов с плавающей запятой, то результат - с плавающей запятой, иначе - целый;
* - если одно из аргументов со знаком, то результат - со знаком, иначе - без знака;
* - результат содержит больше бит (не только значащих), чем максимум в аргументах
* (например, UInt8 + Int32 = Int64).
*/
template <typename A, typename B> struct ResultOfAdditionMultiplication
{
typedef typename Construct<
typename boost::mpl::or_<typename Traits<A>::Sign, typename Traits<B>::Sign>::type,
typename boost::mpl::or_<typename Traits<A>::Floatness, typename Traits<B>::Floatness>::type,
typename Next<typename boost::mpl::max<typename Traits<A>::Bits, typename Traits<B>::Bits>::type>::Type>::Type Type;
};
template <typename A, typename B> struct ResultOfSubtraction
{
typedef typename Construct<
Signed,
typename boost::mpl::or_<typename Traits<A>::Floatness, typename Traits<B>::Floatness>::type,
typename Next<typename boost::mpl::max<typename Traits<A>::Bits, typename Traits<B>::Bits>::type>::Type>::Type Type;
};
/** При делении всегда получается число с плавающей запятой.
*/
template <typename A, typename B> struct ResultOfFloatingPointDivision
{
typedef Float64 Type;
};
/** При целочисленном делении получается число, битность которого равна делимому.
*/
template <typename A, typename B> struct ResultOfIntegerDivision
{
typedef typename Construct<
typename boost::mpl::or_<typename Traits<A>::Sign, typename Traits<B>::Sign>::type,
typename boost::mpl::or_<typename Traits<A>::Floatness, typename Traits<B>::Floatness>::type,
typename Traits<A>::Bits>::Type Type;
};
/** При взятии остатка получается число, битность которого равна делителю.
*/
template <typename A, typename B> struct ResultOfModulo
{
typedef typename Construct<
typename boost::mpl::or_<typename Traits<A>::Sign, typename Traits<B>::Sign>::type,
Integer,
typename Traits<B>::Bits>::Type Type;
};
template <typename A> struct ResultOfNegate
{
typedef typename Construct<
Signed,
typename Traits<A>::Floatness,
typename boost::mpl::if_<
typename Traits<A>::Sign,
typename Traits<A>::Bits,
typename Next<typename Traits<A>::Bits>::Type>::type>::Type Type;
};
template <typename A> struct ResultOfAbs
{
typedef typename Construct<
Unsigned,
typename Traits<A>::Floatness,
typename Traits <A>::Bits>::Type Type;
};
/** При побитовых операциях получается целое число, битность которого равна максимальной из битностей аргументов.
*/
template <typename A, typename B> struct ResultOfBit
{
typedef typename Construct<
typename boost::mpl::or_<typename Traits<A>::Sign, typename Traits<B>::Sign>::type,
Integer,
typename boost::mpl::max<
typename boost::mpl::if_<
typename Traits<A>::Floatness,
Bits64,
typename Traits<A>::Bits>::type,
typename boost::mpl::if_<
2016-05-03 23:19:14 +00:00
typename Traits<B>::Floatness,
Bits64,
typename Traits<B>::Bits>::type>::type>::Type Type;
};
template <typename A> struct ResultOfBitNot
{
typedef typename Construct<
typename Traits<A>::Sign,
Integer,
typename Traits<A>::Bits>::Type Type;
};
/** Приведение типов для функции if:
* 1) void, Type -> Type
* 2) UInt<x>, UInt<y> -> UInt<max(x,y)>
* 3) Int<x>, Int<y> -> Int<max(x,y)>
* 4) Float<x>, Float<y> -> Float<max(x, y)>
* 5) UInt<x>, Int<y> -> Int<max(x*2, y)>
* 6) Float<x>, [U]Int<y> -> Float<max(x, y*2)>
* 7) UInt64 , Int<x> -> Error
* 8) Float<x>, [U]Int64 -> Error
*/
template <typename A, typename B>
struct ResultOfIf
{
typedef
/// 1)
typename boost::mpl::if_<
typename boost::mpl::equal_to<typename Traits<A>::Bits, Bits0>::type,
B,
typename boost::mpl::if_<
typename boost::mpl::equal_to<typename Traits<B>::Bits, Bits0>::type,
A,
/// 4) и 6)
typename boost::mpl::if_<
typename boost::mpl::or_<
typename Traits<A>::Floatness,
typename Traits<B>::Floatness>::type,
typename Construct<
Signed,
Floating,
typename boost::mpl::max< /// Этот максимум нужен только потому что if_ всегда вычисляет все аргументы.
typename boost::mpl::max<
typename boost::mpl::if_<
typename Traits<A>::Floatness,
typename Traits<A>::Bits,
typename ExactNext<typename Traits<A>::Bits>::Type>::type,
typename boost::mpl::if_<
typename Traits<B>::Floatness,
typename Traits<B>::Bits,
typename ExactNext<typename Traits<B>::Bits>::Type>::type>::type,
Bits32>::type>::Type,
/// 2) и 3)
typename boost::mpl::if_<
typename boost::mpl::equal_to<
typename Traits<A>::Sign,
2016-05-03 23:19:14 +00:00
typename Traits<B>::Sign>::type,
typename boost::mpl::if_<
typename boost::mpl::less<
typename Traits<A>::Bits,
typename Traits<B>::Bits>::type,
B,
A>::type,
/// 5)
typename Construct<
Signed,
Integer,
typename boost::mpl::max<
typename boost::mpl::if_<
typename Traits<A>::Sign,
2016-05-03 23:19:14 +00:00
typename Traits<A>::Bits,
typename ExactNext<typename Traits<A>::Bits>::Type>::type,
typename boost::mpl::if_<
2016-05-03 23:19:14 +00:00
typename Traits<B>::Sign,
typename Traits<B>::Bits,
typename ExactNext<typename Traits<B>::Bits>::Type>::type>::type>::Type>::type>::type>::type>::type Type;
};
/** Перед применением оператора % и побитовых операций, операнды приводятся к целым числам. */
template <typename A> struct ToInteger
{
typedef typename Construct<
typename Traits<A>::Sign,
Integer,
typename boost::mpl::if_<
typename Traits<A>::Floatness,
Bits64,
typename Traits<A>::Bits>::type>::Type Type;
};
/// Notes on type composition.
///
/// I. Problem statement.
2016-05-03 23:19:14 +00:00
///
/// Type composition with ResultOfIf is not associative. Example:
/// (Int8 x UInt32) x Float32 = Int64 x Float32 = Error;
/// Int8 x (UInt32 x Float32) = Int8 x Float64 = Float64.
/// In order to sort out this issue, we design a slightly improved version
/// of ResultOfIf.
///
/// II. A more rigorous approach to ResultOfIf.
2016-05-03 23:19:14 +00:00
///
/// First we represent the set of types:
2016-05-03 23:19:14 +00:00
/// T = {Void,Int8,Int16,Int32,Int64,UInt8,UInt16,UInt32,UInt64,Float32,Float64}
/// as a poset P with the partial order being such that for any t1,t2 ∈ T,
2016-05-03 23:19:14 +00:00
/// t1 < t2 if and only if the domain of values of t1 is included in the domain
/// of values of t2.
2016-05-03 23:19:14 +00:00
///
/// For each type t ∈ T, we define C(t) as the set of chains of the poset P whose
/// unique minimal element is T.
///
/// Now for any two types t1,t2 ∈ T, we define the poset C(t1,t2) as the intersection
2016-05-03 23:19:14 +00:00
/// C(t1) ∩ C(t2).
///
/// Denote K(t1,t2) as the unique antichain of C(t1,t2) in which each element minimally
2016-05-03 23:19:14 +00:00
/// represents both t1 and t2. It is important to keep in mind that t1 and t2 are
/// *not* comparable.
///
/// For the most part, K(t1,t2) coincides with the result of the application of
/// ResultOfIf to t1 and t2. Nevertheless, for some particular combinations of t1
/// and t2, the map K returns one of the following two antichains: {Int32,Float32},
/// {Int64,Float64}.
///
/// From these observations, we conclude that the type system T and the composition
/// law ResultOfIf are not powerful enough to represent all the combinations of
/// elements of T. That is the reason why ResultOfIf is not associative.
///
/// III. Extending ResultOfIf.
2016-05-03 23:19:14 +00:00
///
/// Let's embed T into a larger set E of "enriched types" such that:
/// 1. E ⊂ TxT;
/// 2. for each t ∈ T, (T,Void) ∈ E.
/// 3. (Int32,Float32) ∈ E
/// 4. (Int64,Float64) ∈ E.
///
/// E represents the image A of the map K, a set of antichains, as a set of types.
///
/// Consider the canonical injection ψ : T x T ----> E x E and the natural bijection
/// φ : A ----> E.
/// Then there exists a unique map K' : E x E ----> E, that makes the diagram below
/// commutative:
///
/// K
/// T x T ----> A
/// | |
/// | ψ | φ
/// ↓ L ↓
2016-05-03 23:19:14 +00:00
/// E x E ----> E
///
/// L is exactly the same map as K, the sole difference being that L takes as
2016-05-03 23:19:14 +00:00
/// parameters extended types that map to ordinary ones.
///
/// Finally we extend the map L. To this end, we complete the type composition table
/// with the new types (Int32,Float32) and (Int64,Float64) appearing on either
/// the left-hand side or the right-hand side. This extended map is called
/// TypeProduct in the implementation. TypeProduct is both commutative and associative.
2016-05-03 23:19:14 +00:00
///
/// IV. Usage.
2016-05-03 23:19:14 +00:00
///
/// When we need to compose ordinary types, the following is to be performed:
/// 1. embed each type into its counterpart in E with EmbedType;
/// 2. compose the resulting enriched types with TypeProduct;
/// 3. return the first component of the result with ToOrdinaryType, which means
/// that, given an extended type e = (p,q) ∈ E, we return the ordinary type p ∈ T.
2016-05-03 23:19:14 +00:00
///
/// The result is the type we are looking for.
///
/// V. Example.
2016-05-03 23:19:14 +00:00
///
/// Suppose we need to compose, as in the problem statement, the types Int8,
/// UInt32, and Float32. The corresponding embedded types are:
/// (Int8,Void), (UInt32,Void), (Float32,Void).
///
/// By computing (Int8 x UInt32) x Float32, we get:
///
/// TypeProduct(TypeProduct((Int8,Void),(UInt32,Void)), (Float32,Void))
/// = TypeProduct((Int64,Float64), (Float32,Void))
/// = (Float64,void)
/// Thus, (Int8 x UInt32) x Float32 = Float64.
///
/// By computing Int8 x (UInt32 x Float32), we get:
///
/// TypeProduct((Int8,Void), TypeProduct((UInt32,Void), (Float32,Void)))
/// = TypeProduct((Int8,Void), (Float64,Void))
/// = (Float64,void)
/// Thus, Int8 x (UInt32 x Float32) = Float64.
///
namespace Enriched
{
/// Definitions of enriched types.
using Void = std::tuple<void, void>;
using Int8 = std::tuple<DB::Int8, void>;
using Int16 = std::tuple<DB::Int16, void>;
using Int32 = std::tuple<DB::Int32, void>;
using Int64 = std::tuple<DB::Int64, void>;
using UInt8 = std::tuple<DB::UInt8, void>;
using UInt16 = std::tuple<DB::UInt16, void>;
using UInt32 = std::tuple<DB::UInt32, void>;
using UInt64 = std::tuple<DB::UInt64, void>;
using Float32 = std::tuple<DB::Float32, void>;
using Float64 = std::tuple<DB::Float64, void>;
using IntFloat32 = std::tuple<DB::Int32, DB::Float32>;
using IntFloat64 = std::tuple<DB::Int64, DB::Float64>;
}
/// Embed an ordinary type into the corresponding enriched type.
template <typename T>
struct EmbedType;
template <> struct EmbedType<void> { using Type = Enriched::Void; };
template <> struct EmbedType<Int8> { using Type = Enriched::Int8; };
template <> struct EmbedType<Int16> { using Type = Enriched::Int16; };
template <> struct EmbedType<Int32> { using Type = Enriched::Int32; };
template <> struct EmbedType<Int64> { using Type = Enriched::Int64; };
template <> struct EmbedType<UInt8> { using Type = Enriched::UInt8; };
template <> struct EmbedType<UInt16> { using Type = Enriched::UInt16; };
template <> struct EmbedType<UInt32> { using Type = Enriched::UInt32; };
template <> struct EmbedType<UInt64> { using Type = Enriched::UInt64; };
template <> struct EmbedType<Float32> { using Type = Enriched::Float32; };
template <> struct EmbedType<Float64> { using Type = Enriched::Float64; };
/// Get an ordinary type from an enriched type.
template <typename TType>
struct ToOrdinaryType
{
using Type = typename std::tuple_element<0, TType>::type;
};
/// Get an ordinary type from an enriched type.
/// Error case.
template <>
struct ToOrdinaryType<Error>
{
using Type = Error;
};
/// Compute the product of two enriched numeric types.
template <typename T1, typename T2>
struct TypeProduct
{
using Type = Error;
};
/// Compute the product of two enriched numeric types.
/// Case when both of the source types and the resulting type map to ordinary types.
template <typename A, typename B>
struct TypeProduct<std::tuple<A, void>, std::tuple<B, void> >
{
private:
using Result = typename NumberTraits::ResultOfIf<A, B>::Type;
public:
using Type = typename std::conditional<
std::is_same<Result, Error>::value,
Error,
std::tuple<Result, void>
>::type;
};
/// Compute the product of two enriched numeric types.
/// Case when a source type or the resulting type does not map to any ordinary type.
template <> struct TypeProduct<Enriched::Int8, Enriched::UInt16> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::UInt16, Enriched::Int8> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::Int8, Enriched::UInt32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt32, Enriched::Int8> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::Int16, Enriched::UInt16> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::UInt16, Enriched::Int16> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::Int16, Enriched::UInt32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt32, Enriched::Int16> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::Int32, Enriched::UInt32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt32, Enriched::Int32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::Int8> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::Int8, Enriched::IntFloat32> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::Int16> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::Int16, Enriched::IntFloat32> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::Int32> { using Type = Enriched::Int32; };
template <> struct TypeProduct<Enriched::Int32, Enriched::IntFloat32> { using Type = Enriched::Int32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::Int64> { using Type = Enriched::Int64; };
template <> struct TypeProduct<Enriched::Int64, Enriched::IntFloat32> { using Type = Enriched::Int64; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::Float32> { using Type = Enriched::Float32; };
template <> struct TypeProduct<Enriched::Float32, Enriched::IntFloat32> { using Type = Enriched::Float32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::Float64> { using Type = Enriched::Float64; };
template <> struct TypeProduct<Enriched::Float64, Enriched::IntFloat32> { using Type = Enriched::Float64; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::UInt8> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::UInt8, Enriched::IntFloat32> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::UInt16> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::UInt16, Enriched::IntFloat32> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::UInt32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt32, Enriched::IntFloat32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::IntFloat32> { using Type = Enriched::IntFloat32; };
template <> struct TypeProduct<Enriched::IntFloat32, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::IntFloat32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::Int8> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::Int8, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::Int16> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::Int16, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::Int32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::Int32, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::Int64> { using Type = Enriched::Int64; };
template <> struct TypeProduct<Enriched::Int64, Enriched::IntFloat64> { using Type = Enriched::Int64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::Float32> { using Type = Enriched::Float64; };
template <> struct TypeProduct<Enriched::Float32, Enriched::IntFloat64> { using Type = Enriched::Float64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::Float64> { using Type = Enriched::Float64; };
template <> struct TypeProduct<Enriched::Float64, Enriched::IntFloat64> { using Type = Enriched::Float64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::UInt8> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt8, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::UInt16> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt16, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::UInt32> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::UInt32, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
template <> struct TypeProduct<Enriched::IntFloat64, Enriched::IntFloat64> { using Type = Enriched::IntFloat64; };
2011-08-09 15:57:33 +00:00
}
}