2

I want to write a type trait to identify boost multiprecision integer types. I can do this for a concrete type like uint256_t:

template <typename T>
struct is_multiprecision_int : std::false_type {};

template <>
struct is_multiprecision_int<uint256_t> : std::true_type {};

But how can I do the same for ANY boost multiprecision integer (or at least for any multiprecision number with a cpp_int backend)?

Silicomancer
  • 8,604
  • 10
  • 63
  • 130

2 Answers2

3

Note that there is already such a trait - is called is_number and evaluates to true for any instantiation of the number<> class. There is also a trait is_number_expression that detects expression templates derived from operations on number<>.

John Maddock
  • 111
  • 1
  • 2
1

Okay, I'll take a crack at this.

Based on the official documentation for boost-multiprecision: https://www.boost.org/doc/libs/1_73_0/libs/multiprecision/doc/html/boost_multiprecision/tut/ints/cpp_int.html, this may be a possible option:

#include<type_traits>

using namespace boost::multiprecision;

//helper struct for checking if type is cpp_int_backend at compile time using function overloading
template<typename T>
struct is_cpp_int_backend{
    //this template will trigger for pointer to any 
    //  specialisation of cpp_int_backend. Pass by pointer instead
    //  of value, for more generic usage (passing by value will
    //  only work if cpp_int_backend has move constructor defined,
    //  whereas pass-by-pointer will work regardless). References
    //  are also acceptable, however, using pointers will result in
    //  compile error if value is passed, whereas references may give
    //  unexpected behaviour. For these reasons, prefer pointers. 
    template<uint A, uint B, cpp_integer_type CIT, cpp_int_check_type C, typename TT>
    constexpr static std::true_type test(cpp_int_backend<A,B,CIT,C,TT>*);

    //this overload has the lowest precedence during overload
    //  resolution, but will accept any argument.  
    constexpr static std::false_type test(...);

    //type will be std::true_type or std::false_type depending
    //  on which overload is selected. If T is a specialisation
    //  of cpp_int_backend, it will be true_type, else false_type
    using type = decltype(test(std::declval<T*>())); 
    constexpr static bool value = type::value;
};

//use similar technique to above, to create type trait for 
//  multiprecision type
template<typename T>
struct is_multiprecision{

    //enable this template, if T is a specialisation of 'number'
    //  in boost::multiprecision, and the nested template parameter
    //  is a cpp_int_backend. Use pointers for similar reason as 
    //  above
    template<typename TT, typename = std::enable_if_t<is_cpp_int_backend<TT>::value>>
    constexpr static std::true_type test(number<TT>*);

    //again, lowest order of precedence, but will trigger for
    //  anything the above function template does not trigger for
    constexpr static std::false_type test(...);

    //get type depending on T
    using type = decltype(test(std::declval<T*>()));
    constexpr static bool value = type::value; 
};

//variable template for convenience
template<typename T>
constexpr bool is_multiprecision_v = is_multiprecision<T>::value;

//example usage
static_assert(is_multiprecision_v<uint256_t>);

It works on my machine.

Azam Bham
  • 1,389
  • 5
  • 6
  • Thanks a lot! Very helpful! Could you elaborate a little? Why did you use a pointer type as parameter for the true-test function? Also the (...) in the false-test is surprising. – Silicomancer May 06 '20 at 21:33
  • @Silicomancer My mistake, I should've included proper commenting from the beginning. I've made an edit with relevant comments. Let me know if you need more explaining. – Azam Bham May 06 '20 at 23:05
  • The comments help a lot. I am very interested in the ellipsis solution. Does the (...) has advantages over `template constexpr static std::false_type test(TT*);` or `constexpr static std::false_type test(auto);`? – Silicomancer May 07 '20 at 08:48
  • 1
    Yes, `constexpr static std::false_type test(...)` has the lowest possible precedence and will accept absolutely any argument. That means, if you call the function, if no other possible overload exists, only then will the ellipsis function be called. Its cleaner, simpler, more efficient and guaranteed to work compared to your other recommendations. – Azam Bham May 07 '20 at 14:20