Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

clang 3.3 and constexpr constraints

I'm compiling some code with clang 3.3 that seems to compile fine with gcc 4.8:

The original code was:

template <std::size_t N> struct helper { typedef void type; };
template <> struct helper<64> { typedef int64_t type; };
template <> struct helper<32> { typedef int32_t type; };
template <> struct helper<16> { typedef int16_t type; };
template <> struct helper<8>  { typedef int8_t type; };

template <std::size_t I, std::size_t F>
struct test
{
    typedef typename helper<I+F>::type value_type; 
    static constexpr std::size_t frac_mask = ~((~value_type(0)) << F);
};

In clang, if I attempt to declare test<16,16> or test<8,0> I get the error:

test.cpp:41:34: error: constexpr variable 'frac_mask' must be initialized by a constant expression

static constexpr std::size_t frac_mask = ~((~value_type(0)) << F);

Playing around with it, if I convert the code to:

template <std::size_t I, std::size_t F>
struct test
{
    typedef typename helper<I+F>::type value_type; 
    typedef typename std::make_unsigned<value_type>::type mask_type;

    static constexpr mask_type frac_mask = ~((~mask_type(0)) << F);
};

It compiles in most cases (values of I, F), but if I declare test<8, 0>, I get the error:

test.cpp:23:36: error: constexpr variable 'frac_mask' must be initialized by a constant expression

test.cpp:66:15: note: in instantiation of template class 'test<8, 0>' requested here

test.cpp:23:66: note: left shift of negative value -1

   static constexpr mask_type frac_mask = ~((~mask_type(0)) << F);

My question is - is there some rules I'm violating here in terms of the specification of constexpr? Also, for the last error - mask type is unsigned - is this a compiler issue that it thinks I'm shifting a negative value or am I misreading the code?

like image 730
user2721897 Avatar asked Oct 21 '22 02:10

user2721897


1 Answers

In the first case, you're causing signed overflow. One of the conditions for an expression not to be a constant expression, listed in C++11 5.19/2, is that it involves

a result that is not mathematically defined or not in the range of representable values for its type

By using an unsigned type, which is defined to use modular arithmetic, the result remains in range. Presumably, GCC is less strict about this rule than Clang.

In the final case, the unsigned 8-bit type is promoted to int, not an unsigned type, so you get signed overflow again. You can probably fix that by converting back to the unsigned type after negating:

static constexpr mask_type frac_mask = ~(mask_type(~mask_type(0)) << F);

although I'm not quite sure about that, and don't have a Clang installation to test with.

like image 96
Mike Seymour Avatar answered Oct 24 '22 03:10

Mike Seymour