Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behavior with c++ recursive templates when c++11 is enabled

I'm trying to understand some recursive C++ template code I've been handed, and I'm hitting some strange behavior. For some reason, the compiler seems able to add two values at compile-time but doing a left-shift has to be left for run-time. And even then, the problem only occurs if I try to build with c++11 enabled.

The code (which I've boiled down and you will see later) defines two pairs of templates - one pair named shft and shft_aux and one pair named add and add_aux that generate themselves recursively. BTW, the add template is not supposed to be useful, its sole purpose is to demonstrate the problem, not to generate an actual min value.

If I compile this code with no command-line parameters, it compiles just fine. But if I specify -std=c++11 -stdlib=libc++, the static_assert on add_aux is still fine but the static_assert on shft_aux now generates a compile-time error saying static_assert expression is not an integral constant expression.

Why is left-shift treated differently from addition?

Thanks, Chris

p.s. I'm using clang++ version Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)

#include <climits>

template <unsigned size> struct shft; // forward

template <unsigned size>
struct shft_aux
{
    static const int min = shft<size>::min;
};

template <unsigned size>
struct shft
{
    typedef shft_aux<size - 1> prev;
    static const int min = prev::min << CHAR_BIT;
};

// Base specialization of shft, puts an end to the recursion.
template <>
struct shft<1>
{
    static const int min = SCHAR_MIN;
};


// -----

template <unsigned size> struct add; // forward

template <unsigned size>
struct add_aux
{
    static const int min = add<size>::min;
};

template <unsigned size>
struct add
{
    typedef add_aux<size - 1> prev;
    static const int min = prev::min + CHAR_BIT;
};

// Base specialization of add, puts an end to the recursion.
template <>
struct add<1>
{
    static const int min = SCHAR_MIN;
};


// -----

int main()
{
    static_assert(shft_aux<sizeof(int)>::min < 0, "min is not negative");
    static_assert(add_aux<sizeof(int)>::min < 0, "min is not negative");

    return 0;
}
like image 903
Betty Crokker Avatar asked Apr 23 '14 16:04

Betty Crokker


1 Answers

C++11 Standard, [expr.shift]/2

The value of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, [...]. Otherwise, if E1 has a signed type and non-negative value, and E1*2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

[emphasis mine]

This is affected slightly by DR1457 which makes shifting into the "sign-bit" defined behaviour:

Otherwise, if E1 has a signed type and non-negative value, and E1*2E2 is representable in the corresponding unsigned type of the result type [...].

Anyway, in the OP, E1 is negative, so that's still Undefined Behaviour. As such, it is not allowed inside constant expressions:

[expr.const]/2 A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression [...]

  • [...]
  • a result that is not mathematically defined or not in the range of representable values for its type;

That point has changed (DR1313); in n3485 it says:

  • an operation that would have undefined behavior [ Note: including, for example, signed integer over- flow (Clause 5), certain pointer arithmetic (5.7), division by zero (5.6), or certain shift operations (5.8) — end note ];

[class.static.data]/3

If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression


Conclusion: shifting SCHAR_MIN is not a constant expression, so you can't do that in the in-class initializer of a static data member.

Hint: always compile with -Wall -Wextra -pedantic. Using no parameters IMO is a bad idea for g++ and compatible compilers. g++/clang++ per default use the gnu99 mode (see clang doc), which is an extension to C++98 AFAIK. Also, you'll miss many important warnings.

like image 157
dyp Avatar answered Sep 28 '22 18:09

dyp