How do I use the size of a class-static object in its aggregate initializer?
Asked Answered
W

0

6

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.

Widener answered 18/7, 2019 at 22:33 Comment(14)
std::size_t value = sizeof *this; and static constexpr wrapped_float const b{}; seems to compile.Whitsuntide
How about static constexpr wrapped_float const b { sizeof (wrapped_float), 0 };? That seems to compile OK.Benedikta
Perhaps you could give the wrapped_float class a default constructor that sets the value and thereby not need the aggregate initializationTalkie
@melpomene: That only works if the object in question is not const.Widener
@Paul Sanders: Yes, but I would like a statement that explicitly includes the type only once, to make it easier to change in future.Widener
@M.M: The type definition isn't mine; it's from the Win32 headers, and has to be C-compatible. Still, the non-constructor equivalent ("define a constexpr function returning a temporary and hope it gets optimized away") would be a fine answer to question 2 (hint, hint). That still leaves question 1 ("why the different behavior for global variables?") open.Widener
@M.M: I should also add that this also seems to run afoul of the problem I expressed to Paul Sanders; viz., that it would require repeating the type name.Widener
Out of curiosity: Why not struct 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 ExplorerPerigordian
@JacobManaker How is ... constexpr ... const b not a const object?Whitsuntide
@melpomene: It is. My point is that a subsequent b.value = ::value would violate constness, so that qualifier has to be dropped to use your proposal.Widener
@JacobManaker What subsequent b.value = ::value? Where did you get that?Whitsuntide
@melpomene: It seemed implied. What exactly are you proposing, then?Widener
@JacobManaker Changing std::size_t value; //Initialize with sizeof(wrapped_float) to std::size_t value = sizeof *this; and static constexpr wrapped_float const b{ sizeof(b), 0 }; //Error! to static constexpr wrapped_float const b{};.Whitsuntide
@melpomene: Ah. You're the second person to get tripped up by the fact that the type definition isn't mine; it's from the Win32 headers, and has to be C-compatible. I'll add that to the question.Widener

© 2022 - 2025 — McMap. All rights reserved.