It's a fairly common idiom in the Win32 standard library to make the first member of structs be the size of the struct, like so:
#include <cstddef>
struct wrapped_float
{
std::size_t value; //Initialize with sizeof(wrapped_float)
float other; //Actual info
};
If I create a global object, then there is a straightforward initialization syntax that automatically adapts to changes in the type of a
:
constexpr wrapped_float const a{ sizeof(a), 0 }; //OK
Rather than creating a global, I would like my object to be a static member of a struct:
struct test1 {
static constexpr wrapped_float const b{ sizeof(b), 0 }; //Error!
};
But MSVC errors on such code, complaining that b
is not a member of struct test1
. I thus have two related questions:
1. Why is there a difference between global and class-static members?
2. Is there a workaround?
The type in question is from the Win32 headers, so I cannot add or remove members from the equivalent of wrapped_float
. (I can use a derived class/struct, if that would be useful.)
The obvious variations of
struct test2 {
static constexpr wrapped_float const b{ sizeof(test::b), 0 };
};
and
struct test3 {
static constexpr wrapped_float const b{ sizeof(decltype(b)), 0 };
};
produce the same result. I've verified on godbolt that the phenomenon doesn't depend on the fact that I compile with MSVC either — the only compiler for which this works is x86-64 Clang. To conclude a M(non-)WE, write
int main() {
return a.value + test1::b.value + test2::b.value + test3::b.value;
}
Edit, 7/26/19 3:10pm: Question (1) remains wide open. This question suggests that constexpr
variables don't have a size, but this doesn't square with the fact that sizeof(a)
does compile.
For the sake of posterity, I'll summarize some of the workarounds mentioned in the comments so far.
Paul Sanders noted that if you can guarantee that the name of your type won't change, then you could write
struct test4 {
static constexpr wrapped_float const b{ sizeof(wrapped_float), 0 };
};
If you want to reduce the number of explicit references to wrapped_float
, then the following works by NRVO,
template<typename retval_t, typename...Args>
constexpr retval_t make(Args &&... args)
{
return {sizeof(retval_t), std::forward<Args>(args)...};
}
The corresponding constructor version
template<typename obj_ty>
struct auto_size : public obj_ty
{
public:
template<typename...Args>
auto_size(Args &&...args) : obj_ty(sizeof(retval_t), std::forward<Args>...) {}
}
does not work until C++20, b/c base class initialization is by constructor, not aggregate.
std::size_t value = sizeof *this;
andstatic constexpr wrapped_float const b{};
seems to compile. – Whitsuntidestatic constexpr wrapped_float const b { sizeof (wrapped_float), 0 };
? That seems to compile OK. – Benediktaconst
. – Widenerstruct wrapped_float { std::size_t value = sizeof(*this); float other; };
? Not quite what you asked but, at least, it seems to compile in gcc and MSVC: Tried on Compiler Explorer – Perigordian... constexpr ... const b
not a const object? – Whitsuntideb.value = ::value
would violate constness, so that qualifier has to be dropped to use your proposal. – Widenerb.value = ::value
? Where did you get that? – Whitsuntidestd::size_t value; //Initialize with sizeof(wrapped_float)
tostd::size_t value = sizeof *this;
andstatic constexpr wrapped_float const b{ sizeof(b), 0 }; //Error!
tostatic constexpr wrapped_float const b{};
. – Whitsuntide