Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Template Metaprogramming issue in type selection

Tags:

c++11

boost

The following code illustrates my problem

#include <type_traits>
#include <limits>
#include <cstdint>
#include <boost/mpl/if.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>

/////////////////////////////////////////////////////////////////
// safe_signed_range

template <
    std::intmax_t MIN,
    std::intmax_t MAX
>
struct safe_signed_range {
};

/////////////////////////////////////////////////////////////////
// safe_unsigned_range

template <
    std::uintmax_t MIN,
    std::uintmax_t MAX
>
struct safe_unsigned_range {
};

template<class T, class U>
using calculate_max_t = typename boost::mpl::if_c<
    std::numeric_limits<T>::is_signed
    || std::numeric_limits<U>::is_signed,
    std::intmax_t,
    std::uintmax_t
>::type;

template<typename T, typename U>
struct test {

    typedef calculate_max_t<T, U> max_t;
    static_assert(std::is_same<max_t, std::intmax_t>::value, "unexpected value for max_t");
    static_assert(std::is_signed<max_t>::value, "check parameter");

/*
    typedef typename boost::mpl::if_c<
        std::is_signed<max_t>::value,
        safe_signed_range<std::numeric_limits<max_t>::min(), std::numeric_limits<max_t>::max()>,
        safe_unsigned_range<std::numeric_limits<max_t>::min(), std::numeric_limits<max_t>::max()>
    >::type type;
*/

    typedef typename boost::mpl::eval_if_c<
        std::is_signed<max_t>::value,
        boost::mpl::identity<safe_signed_range<std::numeric_limits<max_t>::min(), std::numeric_limits<max_t>::max()> >,
        // error shows up here
boost::mpl::identity<safe_unsigned_range<std::numeric_limits<max_t>::min(), std::numeric_limits<max_t>::max()> >
    >::type type;
};

test<int, int> t1;
//test<int, unsigned> t2;
//test<unsigned, int> t3;
//test<unsigned, unsigned> t4;

int main(){
    return 0;
}

error shows up with Clang compiler as

/Users/robertramey/WorkingProjects/safe_numerics/test/test_z.cpp:116:50: Non-type template argument evaluates to -9223372036854775808, which cannot be narrowed to type 'std::uintmax_t' (aka 'unsigned long')

This looks to like Boost Mpl isn't selecting the correct type. First I suspected (and actually still suspect) that all arguments to the if are being expanded so I changed to use eval_if but still I get the problem. I included static_assert to check the parameters and can make it fail with the simplest of tests - though it fails on all combinations. If anyone can explain my mistake to me, I would be grateful.

like image 740
Robert Ramey Avatar asked Jul 09 '15 22:07

Robert Ramey


1 Answers

The issue is that you're instantiating a template (regardless of boost::mpl::identity) that is invalid for the current type of safe_unsigned_range. The solution to this is to defer the template instantiation based on the boolean predicate passed to boost::mpl::eval_if_c.

To do this we must first we write our own version of identity for both range types:

template<typename Integer, Integer Top, Integer Bottom>
struct defer_unsigned_lazily {
    using type = safe_unsigned_range<Top, Bottom>;
};

template<typename Integer, Integer Top, Integer Bottom>
struct defer_signed_lazily {
    using type = safe_signed_range<Top, Bottom>;
};

The way this works is that the instantiation won't happen until we do typename X::type essentially giving us lazy semantics like boost::mpl::identity.

Then we change the typedef like so:

using limits = std::numeric_limits<max_t>;
typedef typename boost::mpl::eval_if_c<
    std::is_signed<max_t>::value, 
    defer_signed_lazily<max_t, limits::min(), limits::max()>, 
    defer_unsigned_lazily<max_t, limits::min(), limits::max()>
>::type type;

After that it should compile as expected on both Clang and GCC.

Demo

like image 105
Rapptz Avatar answered Nov 12 '22 09:11

Rapptz