Discussion:
RFC: allowing compound assignment operators with designated initializers
Rasmus Villemoes
2018-10-12 20:36:11 UTC
Permalink
This is something I've sometimes found myself wishing was supported. The
idea being that one can say

unsigned a[] = { [0] = 1, [1] = 3, [0] |= 4, ...}

which would end up initializing a[0] to 5. As a somewhat realistic
example, suppose one is trying to build a bitmap at compile time, but
the bits to set are not really known in the sense that one can group
those belonging to each index in a usual | expression. Something like

#define _(e) [e / 8] |= 1 << (e % 8)
const u8 error_bitmap[] = { _(EINVAL), _(ENAMETOOLONG), _(EBUSY), ... }

Writing a small program to generate such a table as part of the build is
not practical in a cross-compile setting (because the constants may only
really be known to the cross-compiler, e.g. the errno values above).

I think the rules would be rather intuitive: If a compound assignment is
used for an element that doesn't have a previous ordinary initializer,
the LHS is 0. Any later ordinary initializer wipes all previous
operations. No operator precedence; the new value is computed
immediately and used as LHS in subsequent operations.

I'm not sure how to define what happens in unions, but I also don't even
know what the current rules are in a case like

union u { char c; int i; } = { .i = 0x11223344, .c = 0x55 }

where one initializes a smaller member after a larger.

Another issue is how to handle side effects in the RHS. It's probably
consistent with the current behaviour to discard all side effects prior
to the last ordinary initializer, and to do the side effects from all
the expressions that did end up affecting the final value. (Btw., the
current documentation doesn't talk about how this interacts with range
initializers, e.g. [0...5] = x++, [2...6] = y++, [0...4] = z++, does x++
happen?) But for automatic variables, one might as well do the compound
operations in code after the declaration, so it would be fine just
allowing this extension for static initialization.

Rasmus
Florian Weimer
2018-10-14 19:45:22 UTC
Permalink
Post by Rasmus Villemoes
This is something I've sometimes found myself wishing was supported. The
idea being that one can say
unsigned a[] = { [0] = 1, [1] = 3, [0] |= 4, ...}
which would end up initializing a[0] to 5. As a somewhat realistic
example, suppose one is trying to build a bitmap at compile time, but
the bits to set are not really known in the sense that one can group
those belonging to each index in a usual | expression. Something like
#define _(e) [e / 8] |= 1 << (e % 8)
const u8 error_bitmap[] = { _(EINVAL), _(ENAMETOOLONG), _(EBUSY), ... }
I think it wouldn't be too hard to extend std::bitset with more
compile-time operations to support this, if that's what you need.
Post by Rasmus Villemoes
Writing a small program to generate such a table as part of the build is
not practical in a cross-compile setting (because the constants may only
really be known to the cross-compiler, e.g. the errno values above).
This is not a problem if you use GCC anyway because you can use inline
assembly quite reliably to extract arbitrary compile-time constants.
Search for gen-as-const-headers in the glibc sources for an example.
Florian Weimer
2018-10-15 18:11:42 UTC
Permalink
Post by Florian Weimer
Post by Rasmus Villemoes
This is something I've sometimes found myself wishing was supported. The
idea being that one can say
unsigned a[] = { [0] = 1, [1] = 3, [0] |= 4, ...}
which would end up initializing a[0] to 5. As a somewhat realistic
example, suppose one is trying to build a bitmap at compile time, but
the bits to set are not really known in the sense that one can group
those belonging to each index in a usual | expression. Something like
#define _(e) [e / 8] |= 1 << (e % 8)
const u8 error_bitmap[] = { _(EINVAL), _(ENAMETOOLONG), _(EBUSY), ... }
I think it wouldn't be too hard to extend std::bitset with more
compile-time operations to support this, if that's what you need.
I didn't doubt that, it's just that I'd expect to be able to use
std::bitset for this.
template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max_element({N...}) / 8;
array<uint8_t, max_index+1> a;
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)), ... };
return a;
}
constexpr uint8_t error_bitmap = make_error_bitmap<EINVAL,
ENAMETOOLONG, EBUSY>();
(This won't compile in C++14 because std::array can't be modified in a
constant expression until C++17).
You wrote that without testing it? I'm impressed. It's really close.

template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max({ N... });
array<uint8_t, max_index+1> a{};
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)) ... };
return a;
}

constexpr auto error_bitmap = make_error_bitmap<EINVAL, ENAMETOOLONG, EBUSY>();

It seems to produce the intended bit pattern.
Of course the response will be "but I don't want to use C++" ...
Indeed.
Gabriel Paubert
2018-10-15 19:08:00 UTC
Permalink
Post by Florian Weimer
Post by Florian Weimer
Post by Rasmus Villemoes
This is something I've sometimes found myself wishing was supported. The
idea being that one can say
unsigned a[] = { [0] = 1, [1] = 3, [0] |= 4, ...}
which would end up initializing a[0] to 5. As a somewhat realistic
example, suppose one is trying to build a bitmap at compile time, but
the bits to set are not really known in the sense that one can group
those belonging to each index in a usual | expression. Something like
#define _(e) [e / 8] |= 1 << (e % 8)
const u8 error_bitmap[] = { _(EINVAL), _(ENAMETOOLONG), _(EBUSY), ... }
I think it wouldn't be too hard to extend std::bitset with more
compile-time operations to support this, if that's what you need.
I didn't doubt that, it's just that I'd expect to be able to use
std::bitset for this.
template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max_element({N...}) / 8;
array<uint8_t, max_index+1> a;
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)), ... };
return a;
}
constexpr uint8_t error_bitmap = make_error_bitmap<EINVAL,
ENAMETOOLONG, EBUSY>();
(This won't compile in C++14 because std::array can't be modified in a
constant expression until C++17).
You wrote that without testing it? I'm impressed. It's really close.
template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max({ N... });
array<uint8_t, max_index+1> a{};
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)) ... };
return a;
}
Hmm, isn't the array roughly 8 times too large?

IOW, shouldn't you declare "array<uint8_t, (max_index+7)/8> a{};" ?
Post by Florian Weimer
constexpr auto error_bitmap = make_error_bitmap<EINVAL, ENAMETOOLONG, EBUSY>();
It seems to produce the intended bit pattern.
Did you think of big-endian machines (just curious)?

Gabriel
Post by Florian Weimer
Of course the response will be "but I don't want to use C++" ...
Indeed.
Gabriel Paubert
2018-10-15 21:33:07 UTC
Permalink
Post by Gabriel Paubert
Post by Florian Weimer
Post by Florian Weimer
Post by Rasmus Villemoes
This is something I've sometimes found myself wishing was supported. The
idea being that one can say
unsigned a[] = { [0] = 1, [1] = 3, [0] |= 4, ...}
which would end up initializing a[0] to 5. As a somewhat realistic
example, suppose one is trying to build a bitmap at compile time, but
the bits to set are not really known in the sense that one can group
those belonging to each index in a usual | expression. Something like
#define _(e) [e / 8] |= 1 << (e % 8)
const u8 error_bitmap[] = { _(EINVAL), _(ENAMETOOLONG), _(EBUSY), ... }
I think it wouldn't be too hard to extend std::bitset with more
compile-time operations to support this, if that's what you need.
I didn't doubt that, it's just that I'd expect to be able to use
std::bitset for this.
template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max_element({N...}) / 8;
array<uint8_t, max_index+1> a;
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)), ... };
return a;
}
constexpr uint8_t error_bitmap = make_error_bitmap<EINVAL,
ENAMETOOLONG, EBUSY>();
(This won't compile in C++14 because std::array can't be modified in a
constant expression until C++17).
You wrote that without testing it? I'm impressed. It's really close.
template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max({ N... });
array<uint8_t, max_index+1> a{};
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)) ... };
return a;
}
Hmm, isn't the array roughly 8 times too large?
Yes, it looks like I pasted the wrong version of the code, which is
why it had the stray comma that Florian corrected. The final version
#include <array>
#include <algorithm>
#include <cstdint>
template<int... N>
constexpr auto
make_error_bitmap()
{
using std::uint8_t;
using std::array;
constexpr auto max_index = std::max({N...}) / 8;
array<uint8_t, max_index+1> a{};
[[maybe_unused]] uint8_t sink[] = { a[N/8] |= (1 << (N%8)) ... };
return a;
}
Looks like it no more wastes memory now.
constexpr auto error_bitmap = make_error_bitmap<EINVAL, ENAMETOOLONG, EBUSY>();
(Note that max_index has the division by 8)
Post by Gabriel Paubert
IOW, shouldn't you declare "array<uint8_t, (max_index+7)/8> a{};" ?
And this expression had an off-by-one error, sorry.
Post by Gabriel Paubert
Post by Florian Weimer
constexpr auto error_bitmap = make_error_bitmap<EINVAL, ENAMETOOLONG, EBUSY>();
It seems to produce the intended bit pattern.
Did you think of big-endian machines (just curious)?
The code does what the OP asked for, I didn't try to figure out if
what it did made sense.
Ok.

Gabriel

Loading...