6
#include <iostream>

union gc_bits {
    size_t value;
    struct {
        size_t arena : 2;
    } bits;

    constexpr gc_bits(size_t value_) : value(value_) {
    }
};

static constexpr size_t get_max_arenas() {
    return gc_bits(~0ULL).bits.arena;
}

size_t current_colour[get_max_arenas()]; // error

int main() {
    std::cout << get_max_arenas() << std::endl;
}

The array declaration errors out because get_max_arenas is not a constexpr. I'm not clear on why this should be so.

timrau
  • 22,578
  • 4
  • 51
  • 64
DrPizza
  • 17,882
  • 7
  • 41
  • 53
  • 2
    By the way, you write to `value` and then read from `bits` in the union. Isn't that undefined behaviour in C++? – simon Sep 17 '15 at 06:39
  • I don't know, you're allowed to read from the common prefixes in unions, and since both value and bits start with a size_t I think it should be allowed. – DrPizza Sep 17 '15 at 06:42
  • By the way 2: The standard doesn't say if the two bits are from the left or right end of the size_t. Limits portability. I would try with a shift and mask. – Bo Persson Sep 17 '15 at 06:58
  • 3
    @DrPizza: there's a provision for reading from either of two `struct`s embedded in a `union`, when those `struct`s are standard-layout and share a common initial sequence of members, but only one of your members is a `struct` (and the provision for two `struct`s explicitly requires both to be bit fields of the same width, or neither to be a bit field). – Tony Delroy Sep 17 '15 at 07:00
  • @BoPersson: that would matter if it were all of `arena`'s bits being set, then `value` being read, but here all bits in `value` are set and given `arena` overlaps it can only be `3` (which may or may not have needed shifting to extract), if it all weren't undefined behaviour for other reasons. – Tony Delroy Sep 17 '15 at 07:03
  • @TonyD - If we *know* that the result is 3, we could skip all of this. :-) I assumed that DrPizza would use the bits elsewhere too. – Bo Persson Sep 17 '15 at 07:05
  • 2
    Type-punning using unions is [not undefined in C but is in C++](http://stackoverflow.com/a/11996970). Once you invoke UB all bets are off, so I dunno if the question still holds. – legends2k Sep 17 '15 at 07:26
  • @BoPersson: the client code `gc_bits(~0ULL).bits.arena;` almost (but for undefined behaviour) serves to get the maximum value for `arena` without knowing the bit-field width: couldn't replace that with a hard-coded `3` without introducing an extra point of maintenance. – Tony Delroy Sep 17 '15 at 07:28
  • @TonyD: Exactly; I don't want to have to make reference to the bitfield width in more than one place (because I want to ensure that the different values are always in sync), but I fear I may have to. I'm struggling to see why the C++ spec makes unions and bit fields quite so limited, especially when C11 makes them somewhat more capable. – DrPizza Sep 17 '15 at 15:55
  • @DrPizza: there are ways to do this without aliasing - e.g. [here](http://coliru.stacked-crooked.com/a/84d88224504bdfc5) – Tony Delroy Sep 25 '15 at 05:18
  • @TonyD: Hmm, that seems to work, though I'm not tremendously clean on having a second (bogus) constructor. Do you see any clean way to do this initializing multiple fields, without having to put them all in that bogus constructor? This is one of the things that would be nice with the punning; one-shot initialization of the whole bitfield. – DrPizza Sep 25 '15 at 22:04
  • @DrPizza: you can use `memcpy()` from any all-bits-set memory - the compiler's required to re-read the bit fields from memory afterwards - and probably `memset()`, which would be cleaner, though I haven't seen that discussed explicitly in the context of aliasing. – Tony Delroy Sep 26 '15 at 03:22

1 Answers1

7

Slightly rephrasing your program:

static constexpr auto gma = get_max_arenas();

size_t current_colour[gma]; // error

gives the Clang error:

read of member 'bits' of union with active member 'value' is not allowed in a constant expression

The reason you get this error is that the constructor sets the value, and then you try to read bits. This is not allowed, as commented by @gurka.

Standard quote:

[expr.const]

2 A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:

(2.8) — an lvalue-to-rvalue conversion (4.1) or modification (5.18, 5.2.6, 5.3.2) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • @DrPizza no it's not arbitrary: at runtime, it's [undefined behavior](http://stackoverflow.com/q/11373203/819272) to access a non-active union member, with no diagnostic required, whereas at compile-time, you get the extra service of a compiler error. – TemplateRex Sep 17 '15 at 17:29
  • If you believe it is not arbitrary then please explain the rationale for not allowing type punning of types with trivial constructors, and in your answer please make sure to include an explanation of why C11 is wrong to permit this very thing. – DrPizza Sep 17 '15 at 19:30
  • @DrPizza I am not aware of the rationale for having that particular part in the Standard, so you might be right it's somewhat arbitrary, but given that it is undefined behavior at runtime, it's actually good to get an error at compile-time when used in a `constexpr` context. – TemplateRex Sep 17 '15 at 19:37
  • 1
    @DrPizza In general, C++ is stricter than C to label certain language constructs as undefined behavior. This can help compilers make better optimizations. It's a tradeoff, because sometimes it also disallows code that on most machines would generate reasonable code. – TemplateRex Sep 17 '15 at 19:40
  • 1
    Sure, I can see that, but I'd rather have C11-style type punning than compile-time enforcement of runtime-undefined behaviour. – DrPizza Sep 17 '15 at 20:15
  • @DrPizza: *"rather...type punning than compile-time enforcement of runtime-undefined behaviour"* - as TemplateRex said, the choice is between extra optimisations and defined behaviour accessing non-active members. E.g. `void f(int a, int b) { if (a) my_union.my_float = 0.3; else my_union.my_int = 27; ...la-de-dah...; if (b) std::cout << my_union.my_float; }` in C++ the compiler can assume `b` will be non-zero whenever `a` is (otherwise it's a programmer error), and reuse a floating-point register storing 0.3 when calling `cout`. Faster than forcing treatment as `volatile`/store+read of RAM. – Tony Delroy Sep 18 '15 at 04:54
  • If C can maintain decent performance without this kind of optimization then I'm quite sure C++ can too. – DrPizza Sep 18 '15 at 14:21