Article URL: https://ryanjk5.github.io/posts/rjk-duck/ Comments URL: https://news.ycombinator.com/item?id=48905914 Points: 13 # Comments: 5

If you’ve ever tried using type erasure for something more complicated than std::any or std::function, you’ve either written 100+ lines of easy-to-mess-up code or reached for a boilerplate-heavy library like Boost.TypeErasure or Folly.Poly. rjk::duck uses the magic that is C++26 reflection to remove these pain points while preserving all of the customization and performance. Simply declare the interface once and let your already-written definitions handle the rest. The library itself is a single header include. It offers owning and non-owning semantics, operators, interface composition, adapters for existing interfaces, extension methods for third-party types, and much more. We’re talking about the bleeding edge here, so right now support is only available for gcc with -std=c++26 -freflection. duck uses reflection in some unique ways that go beyond the simple enum-to-string or JSON serialization examples you may have seen, so we’ll spend the rest of the article demystifying the tricks that make this library possible. In particular, we’ll walk through tag generation, vtable codegen, overload resolution, and the interconvertibility trick that keeps duck small. You may have noticed the strange bit of syntax from the above example, [[=rjk::trait]]. This is a C++26 annotation, which can be applied to a struct similarly to an attribute. The actual definition for trait is simply: The ^^ operator produces a reflection of something. In this case, we are reflecting both a type (MyType) and a variable (trait). Reflections all have the std::meta::info type and can be queried with a variety of meta-functions, like annotations_of and type_of. One of the first steps in duck’s process is interpreting the members of a trait and converting them to a tag, which is a format that duck uses internally. So for this trait: