6

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;
}
hivert
  • 10,579
  • 3
  • 31
  • 56
Betty Crokker
  • 3,001
  • 6
  • 34
  • 68
  • Frankly, I don't quite understand what you want to do with that.. maybe there's a *solution* to your problem and not just some language-lawyer remark about what the Standard allows. – dyp Apr 23 '14 at 17:27
  • It's like the old joke ... "Doctor, it hurts when I do this" ... "So don't do that!" Now that I fully understand the issue, and have the backing of the Standard, I feel confident in proposing a different way of solving the problem that doesn't require left-shifting of negative numbers. – Betty Crokker Apr 24 '14 at 16:24

1 Answers1

7

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.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • 1
    Somehow I get the impression quoting the Standard itself is not very useful. Maybe I should stick to my motto "n3485 is the best draft for C++11". – dyp Apr 23 '14 at 17:22
  • I'm very grateful that you quoted the Standard! Did you get some negative feedback for doing so? – Betty Crokker Apr 24 '14 at 16:20
  • @BettyCrokker I used to only quote from n3485; mixing both n3485 and the International Standard could be confusing. The differences typically only matter for language-lawyers, since most compiler maintainers eventually implement the proposed resolutions of defects. Also, the differences don't really matter for your question, so they might be just noise here. – dyp Apr 24 '14 at 18:23
  • @dyp I remembered this comment you made on `N3485` and I was wondering if you have anything to add to [this comment thread on N3485](http://stackoverflow.com/questions/25047109/classes-with-both-template-and-non-template-conversion-operators-in-the-conditio#comment38992224_25047109). – Shafik Yaghmour Jul 31 '14 at 18:02