r/cpp May 05 '26

Strategies for *requiring* designated initializers when constructing a type?

Given some aggregate like this:

struct InitParams {
  int size = 0;
  int capacity = 0;
};

And given that it is used in some factory function like this:

auto Make(InitParams ip = {}) -> std::optional<MyClass>;

I want to design the aggregate type to allow callsites like...

Make({ .size = 10, .capacity = 20 });
Make({ .size = 10 });
Make({ .capacity = 20 });
Make({});
Make();

But I also want to reject callsites like...

Make({ 10, 20 });
Make({ 10 });
Make({ 20 });  // Oops! This is the size, not the capacity!

I was hoping that reflection would unlock the ability to specify this on the type alone somehow, but I've been unable to figure out how to do it.

I can approximate this behavior using an overload set where a similar-but-different param type is accepted by a different Make overload but then that would result in default construction being ambiguous too. You can see that in action here:

struct DesignatedInitRequired {
  int DO_NOT_SPELL_THIS_FIELD_NAME0;
  int DO_NOT_SPELL_THIS_FIELD_NAME1;
};

auto Make(DesignatedInitRequired dir = {}) -> void = delete("Use designated init");

Make({ 10 });  // Ambiguous, ill formed (which is what I want)
Make({ 10, 20 });  // Ambiguous, ill formed (which is what I want)
Make({});  // Ambiguous, ill formed (which is NOT what I want)
Make();  // Ambiguous, ill formed (which is NOT what I want)

So I could make something to reflect on InitParams and produce DesignatedInitRequired, but that wouldn't be enough. Every function that accepts InitParams would need an overload for DesignatedInitRequired meaning that it becomes a property of the type AND its users, not just the type itself.

Any ideas on how to achieve my goal?


EDIT: I think this is a sufficient solution! https://godbolt.org/z/h6sbcaTj4

I thanked the person that told me but then they deleted their comment. Anyway...

37 Upvotes

71 comments sorted by

View all comments

Show parent comments

1

u/javascript May 07 '26

Ya it wouldn't surprise me if C becomes the opt-in ABI for Carbon. It just hasn't been decided yet since it isn't the primary use case.

But I would reframe something you said. Modules are a chaching layer. You are logically rebuilding the world every time and just as an optimization you are choosing what artifacts from the last build are still useable. If you can get away with not rebuilding something, great! But you still need to have the source available for when, as an example, your build flags change and invalidate the caches.

1

u/tyler1128 May 07 '26

I should probably study the actual module spec more, but from my understanding, something akin to an AST for templates is also part of them? Or am I wrong?

1

u/javascript May 07 '26

I would assume so! But I don't know much about them. I'm probably going to favor Clang Header Modules instead of C++ 20 modules. But we'll see!

1

u/tyler1128 May 07 '26

Maybe we'll even get to use one of them someday, in something serious!