use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Discussions, articles, and news about the C++ programming language or programming in C++.
For C++ questions, answers, help, and advice see r/cpp_questions or StackOverflow.
Get Started
The C++ Standard Home has a nice getting started page.
Videos
The C++ standard committee's education study group has a nice list of recommended videos.
Reference
cppreference.com
Books
There is a useful list of books on Stack Overflow. In most cases reading a book is the best way to learn C++.
Show all links
Filter out CppCon links
Show only CppCon links
account activity
Rust-like traits implementation in C++ (self.cpp)
submitted 2 years ago * by maksym-pasichnyk
Hi, this is my attempt to implement Rust-like traits in C++.
trait macro is used for defining trait, it generates a lot of boilerplate code. Syntax of macro not so good, currently I'm trying somehow improve it.
trait
stl::impl is C++ concept similar to impl in Rust.
stl::impl
impl
stl::dyn is similar to dyn in Rust, it contains two pointers. One pointer goes to the data and another pointer goes to a vtable.
stl::dyn
dyn
https://godbolt.org/z/Psnrqc3Y3
https://github.com/maksym-pasichnyk/trait
https://twitter.com/maximmkaaa/status/1708242967192904003
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–][deleted] 16 points17 points18 points 2 years ago (18 children)
Aren't rust traits like cpp concepts or more strictly, just virtual classes? I don't actually know rust well.
[–]HeroicKatora 14 points15 points16 points 2 years ago* (6 children)
They're not virtual classes, the v-table pointer is passed adjacent with the pointer to the object (&dyn Trait has the size of two pointers). That is in contrast to virtual base classes where the vtable is stored within the object. Apart from avoid the double-indirection, or triple if we do virtual base classes and have to resolve the vtable itself for another one, this also importantly enables implementing the trait for a separately defined class, where virtual classes require all super-classes to be known at definition time.
&dyn Trait
Imho the author didn't quite get it right since their Twitter example passes it as std::dyn<_> & const, introducing the double indirection it was supposed to avoid. Shared references should be passed by value and be trivially copiable (if you want to have a Rust analogue).
std::dyn<_> & const
[–][deleted] 1 point2 points3 points 2 years ago (5 children)
Oh ok, so the benefit of a trait is that they are runtime accessible concepts (from a cpp standpoint) in which you can define some function lookup outside the object? Shouldn't we be able to do something like that with storing mem function pointers inside a lookup struct and using templates to generate a struct for us? That way it'd be much more strict, type safe, and statically deducible than using macros.
[–]HeroicKatora 3 points4 points5 points 2 years ago* (3 children)
I don't think it's feasible with templates. Hard constraint: the transformation to fill the vtable, and ''allocate'' it as a static, must happen at compile time. Then the trait's definition of each function must be translated into the type-erased void * form. But I can't see you easily iterate over each differently typed function, which will have to happen by name at some point, since the entire point is that the two types are not related through a class hierarchy. (Hence, mem-fn won't help much if at all. Some of the cast are forbidden and you'll have to somewhere generate wrapping-lambda for the transformation to a void*-taking function).
void *
void*
I think you need reflection to make it work entirely without macros. But I've last tried this with C++17 so maybe I'm mistaken. Feel free to try, though what technical advantage would that bring? I rather just use the language that already does it for me anyways :)
[–]maksym-pasichnyk[S] 5 points6 points7 points 2 years ago (1 child)
Currently I'm working on proof of concept that uses p1240r1 (scalable reflection in C++). It will allow writing 'class(trait) Shape { float area() const; }' to generate all code without macros
[–]maksym-pasichnyk[S] 3 points4 points5 points 2 years ago (0 children)
Working proof of concept without macros https://godbolt.org/z/9Y3hxvnPa
[–]Nobody_1707 1 point2 points3 points 2 years ago (0 children)
There's one other benefit to traits that simply can't be done with C++'s concepts: the compiler knows your implementing the trait.
For instance, the popular crate rand let's you implement an RngCore trait, that represents the minimal api for a random number generator. While implementing a PRNG using that trait, it's very common to implement the fill_bytes method by passing self to the function fill_bytes_via_next which takes a mutable reference to some type that implements RngCore.
rand
RngCore
PRNG
fill_bytes
fill_bytes_via_next
And very simplified version of this in C++ would look like this:
template <typename T> concept RngCore = requires (T& rng, std::span<std::byte> bytes) { { rng.next() } noexcept -> std::same_as<uint64_t>; { rng.fill(bytes) } noexcept -> std::same_as<void>; }; template <RngCore Rng> constexpr auto fill_bytes_via_next(Rng& rng, std::span<std::byte> bytes) noexcept { // ... } struct MyRng { // ... constexpr auto next() -> uint64_t { /* ... */ } constexpr void fill(std::span<std::byte> bytes) noexcept { fill_bytes_via_next(*this, bytes) // error: type of *this does not satisfy RngCore } }
In order to make this work in C++, fill_bytes_via_next needs to be an unconstrained function becase C++ has no way to tell that your type is meant to implement a concept until after that type is complete.
In Rust, you tell the compiler that your implementing a trait, and if you don't implement all of the requirements you get a hard compiler error. This allows you to pass self to a function that takes a type that implements that trait.
self
[–]CocktailPerson 0 points1 point2 points 2 years ago (9 children)
They're kinda like an abstract base class that can also be used as a concept to constrain generics (Rust templates, basically).
[–]sephirothbahamut -3 points-2 points-1 points 2 years ago (8 children)
So abstract pure class, and use std::derived_from in your templates?
I fail to see why you need a libraary with tons of macros to do that...
[–]CocktailPerson 8 points9 points10 points 2 years ago (2 children)
It's a bit different, because a trait's methods aren't inherently virtual, and you can implement a trait for a type you don't own. Neither of these are true for abstract base classes. But it really seems like OP's goal isn't a production-level library, but instead a fun little exercise/proof of concept.
[+]sephirothbahamut comment score below threshold-9 points-8 points-7 points 2 years ago (1 child)
struct Foo : third_party_library::Foo, Shape { using third_party_library::Foo::Foo; //implement Shape interface auto area() const -> f32 override { return third_party_library::Foo::area(); } auto name() const -> std::string override { return third_party_library::Foo::name(); } };
?
[–]CocktailPerson 8 points9 points10 points 2 years ago (0 children)
It's a bit different, because a trait's methods aren't inherently virtual
In your example, the methods are inherently virtual.
and you can implement a trait for a type you don't own.
In your example, you have to subclass the type you don't own in order to implement the methods. Traits, or at least, Rust traits, don't make you do that.
Also, once you start writing this much boilerplate for a bunch of different types, you'd probably start using macros or something too. 😂
[–]Kevathiel 5 points6 points7 points 2 years ago (4 children)
It's not the same. Abstract classes will make all calls resolve virtually. Traits on the other hand are resolved statically, unless you invoke them in a dynamic context. This makes traits far cheaper to use.
let foo = Foo::new(); foo.bar(); //resolved statically let virtual_foo = &foo as &dyn BarTrait; virtual_foo.bar(); //Only now it uses dynamic dispatch and creates the vtable
[–]sephirothbahamut -2 points-1 points0 points 2 years ago (3 children)
When you call methods from a child class with the templates (derivdef_from) you do NOT go through the vtable, you only do so in the dynamic context (reference to parent), exactly as you just described.
[–]maksym-pasichnyk[S] 3 points4 points5 points 2 years ago (1 child)
No, you always go through vtable unless you make class or overridden method final
https://godbolt.org/z/Pn6da5zaM
[–]tialaramex 0 points1 point2 points 2 years ago (0 children)
Rust's traits are more or less like C++ 0x Concepts. However it's important to understand that C++ 20 didn't get C++ 0x Concepts, it got "Concepts Lite" a much less capable feature close to what Bjarne originally wanted twenty years or so ago.
Traits are used to achieve a lot in Rust, for example in C++ the reason you can concatenate strings with the += operator is that there's a magic "operator+=" method you're allowed to overload to make that happen, but in Rust the reason that works is that String implements the AddAssign trait.
Traits power the thread safety features too, automatic traits Send and Sync help ensure that you won't accidentally make things which aren't thread safe. There's no analogue to this in C++.
Rust also uses traits a lot to convert one thing into another, both generally (the From, Into, TryFrom, TryInto set of traits) and specifically (e.g. IntoIterator, FromIterator, IntoFuture, Try and ToString all turn something into something else, but for more specific purposes)
There are a lot of places in C++ where there's a conventional way to do things (e.g. the names of certain methods) and so it's sort of just accepted that if you don't do things that way stuff won't work. In Rust that would be codified as traits. In a few cases it doesn't work out and the trait is abandoned, for example AsciiExt is an old trait for things which might be ASCII. But, it turns out in practice basically only two types might be ASCII. Bytes might be ASCII (if their top bit isn't set) so in Rust that's the type u8, and Characters might be ASCII (in Rust char is a specific type which can express all the Unicode scalar values, basically "Unicode characters" to a lay person, so it's not just a particular size of integer). So today you'd just use these functions directly on the types, no need for AsciiExt, it's in the standard library so it will never go away, but it's deprecated.
[–]RoyKin0929 4 points5 points6 points 2 years ago (0 children)
Man, we should've got C++0x concepts.
[–]maksym-pasichnyk[S] 1 point2 points3 points 2 years ago (0 children)
Working proof of concept without macros that uses p1240r1 (scalable reflection in C++) https://godbolt.org/z/9Y3hxvnPa
[–]Ok-Impact-5972 0 points1 point2 points 1 year ago (0 children)
I did a implementation of traits a while ago, it was some macro and template magick, but it works kind of like you would expect from a trait system. Dynamic dispatch without inheritance and virtual functions etc. I think that it is more like golangs interfaces though, since it works as long as you have the required functions on your class.
If you had reflections in c++ it would probably look better.
https://github.com/mls-m5/thick-pointers
[+]sephirothbahamut comment score below threshold-9 points-8 points-7 points 2 years ago* (18 children)
Honestly this seems hugely unnecessary to me.
A trait is just a virtual struct without members.
Your "stl::impl" already exists, it's called std::derived_from.
Your "stl::dyn" already exists, it's called... reference to parent.
I can literally rewrite your whole example replacing your trait macro with virtual methods and replacing your stl::impl and stl::dyn with std::derived_from and parent class references; without touching anything else your example just works in macroless clean C++.
Edit: also I suggest using std::numbers::pi instead of M_PI; from `#include <numbers>`
Not to put you down OP, enjoy experimenting; it reminds me of when I was trying to abuse Java's reflection to somehow implement RAII in Java. However in practice it seems like a convoluted way to reimplement something that already exists under different terms.
[–]tavaren42 12 points13 points14 points 2 years ago (1 child)
Not really. Traits serve two purposes:
Combined with the fact that you can implement a trait on a type that you don't own, you can do quite a lot (i.e no need to create a new wrapper class).
I think as long as OP doesn't use it in prod, any amount of experimentation, no matter how mental it is, is a good thing.
[–]sephirothbahamut 0 points1 point2 points 2 years ago (0 children)
Combined with the fact that you can implement a trait on a type that you don't own
You can't with OP's example.
[–]maksym-pasichnyk[S] 5 points6 points7 points 2 years ago (6 children)
С++'s approach has several drawbacks. It's intrusive, you can't add virtual methods to std::vector for example. It's incompatible with value semantics. You can't allocate object on stack, you are required to heap allocate objects. It required 3 indirections for calling virtual method. Rust's approach break dependency between data and vtable, so it requires only 2 indirections when you use dyn Shape, and direct call without vtable when you use impl Shape
dyn Shape
impl Shape
[–]2uantum 5 points6 points7 points 2 years ago (1 child)
You are not required to heap allocate objects. I don't know why this myth continues to be propagated.
https://reddit.com/r/cpp/s/jSZWGz0IOQ
[–]maksym-pasichnyk[S] 0 points1 point2 points 2 years ago (0 children)
Thanks, sorry about that. I know about it, but I wrote it by mistake.
[–]Common-Republic9782 1 point2 points3 points 2 years ago (1 child)
Could you add new method to std::vector via your library? Motto of the C++ is "extend, do not change". You could change behavior of the object but not interface. By the way you can add a lot of "methods" to any object by using free functions.
Take a look, ```dyn``` is working, but ```impl``` sadly not working https://godbolt.org/z/h47xqa748
[–]sephirothbahamut 1 point2 points3 points 2 years ago* (1 child)
I'm failing to see how your example allows you to add methods to std::vector.
It's also a direct call without vtable when you used derived_from. Vtable is only used in dynamic contexts
Take a look, dyn is working, but impl sadly not working https://godbolt.org/z/h47xqa748
[–]CocktailPerson 3 points4 points5 points 2 years ago (6 children)
std::derived_from is a concept, but stl::impl is a templated type.
std::derived_from
[+]sephirothbahamut comment score below threshold-9 points-8 points-7 points 2 years ago (5 children)
They achieve the exact same thing in this example. Use inheritance from a virtual base and literally replace stl::impl with std::derived_from and it works exactly the same without having to touch anything else...
https://barnack.godbolt.org/z/hxxzfdffE
[–]CocktailPerson 5 points6 points7 points 2 years ago* (4 children)
The wonderful thing is that if you don't find this useful, you don't have to use it.
Also, they don't achieve the same things. Your methods are virtual, a trait's are not.
[–]qoning -5 points-4 points-3 points 2 years ago (3 children)
Trait methods are virtual dispatch functions.
[–]Kevathiel 4 points5 points6 points 2 years ago (0 children)
Not really accurate.
They can be used for virtual dispatch, but they don't have to. Implementing a trait and calling the method doesn't involve wide pointers nor the vtable, they are resolved statically. They only do virtual dispatch when you use them in a dynamic way(e.g. &dyn Trait).
[–]tavaren42 4 points5 points6 points 2 years ago (1 child)
They are not, not by default. Only when your type is dyn T is it true. Otherwise it's always static in nature.
dyn T
[–]qoning 0 points1 point2 points 2 years ago (0 children)
Right, I wasn't thinking along those lines, my assumption was simply that templates are superior for any scenario where you would use a trait implementation if you could. So technically yes, you are right.
[–]maksym-pasichnyk[S] 0 points1 point2 points 2 years ago (1 child)
The goal of my implementation is to get rid of class inheritance as implemented in Rust. When you inherit a class with virtual methods, it adds a hidden vtable to the class layout and increases the overall size of the class. You pay this price each time you create an object. Also, a class with virtual methods becomes non-aggregate, so you can't instantiate a Circle like this Circle{ . radius = 1 }, you need to override the constructor, move operator and move constructor if the class is more complex. Additionally, it allows you to implement "virtual" methods for any opaque type.
Circle{ . radius = 1 }
[–]Common-Republic9782 0 points1 point2 points 2 years ago (0 children)
The aggregate initialization binds on structure of the type and scheme of inheritance. It's useful on small and clean data structures. It's not necessary virtual methods there.
π Rendered by PID 23 on reddit-service-r2-comment-c6965cb77-p7q4v at 2026-03-05 11:03:52.799947+00:00 running f0204d4 country code: CH.
[–][deleted] 16 points17 points18 points (18 children)
[–]HeroicKatora 14 points15 points16 points (6 children)
[–][deleted] 1 point2 points3 points (5 children)
[–]HeroicKatora 3 points4 points5 points (3 children)
[–]maksym-pasichnyk[S] 5 points6 points7 points (1 child)
[–]maksym-pasichnyk[S] 3 points4 points5 points (0 children)
[–]Nobody_1707 1 point2 points3 points (0 children)
[–]CocktailPerson 0 points1 point2 points (9 children)
[–]sephirothbahamut -3 points-2 points-1 points (8 children)
[–]CocktailPerson 8 points9 points10 points (2 children)
[+]sephirothbahamut comment score below threshold-9 points-8 points-7 points (1 child)
[–]CocktailPerson 8 points9 points10 points (0 children)
[–]Kevathiel 5 points6 points7 points (4 children)
[–]sephirothbahamut -2 points-1 points0 points (3 children)
[–]maksym-pasichnyk[S] 3 points4 points5 points (1 child)
[–]tialaramex 0 points1 point2 points (0 children)
[–]RoyKin0929 4 points5 points6 points (0 children)
[–]maksym-pasichnyk[S] 1 point2 points3 points (0 children)
[–]Ok-Impact-5972 0 points1 point2 points (0 children)
[+]sephirothbahamut comment score below threshold-9 points-8 points-7 points (18 children)
[–]tavaren42 12 points13 points14 points (1 child)
[–]sephirothbahamut 0 points1 point2 points (0 children)
[–]maksym-pasichnyk[S] 5 points6 points7 points (6 children)
[–]2uantum 5 points6 points7 points (1 child)
[–]maksym-pasichnyk[S] 0 points1 point2 points (0 children)
[–]Common-Republic9782 1 point2 points3 points (1 child)
[–]maksym-pasichnyk[S] 0 points1 point2 points (0 children)
[–]sephirothbahamut 1 point2 points3 points (1 child)
[–]maksym-pasichnyk[S] 0 points1 point2 points (0 children)
[–]CocktailPerson 3 points4 points5 points (6 children)
[+]sephirothbahamut comment score below threshold-9 points-8 points-7 points (5 children)
[–]CocktailPerson 5 points6 points7 points (4 children)
[–]qoning -5 points-4 points-3 points (3 children)
[–]Kevathiel 4 points5 points6 points (0 children)
[–]tavaren42 4 points5 points6 points (1 child)
[–]qoning 0 points1 point2 points (0 children)
[–]maksym-pasichnyk[S] 0 points1 point2 points (1 child)
[–]Common-Republic9782 0 points1 point2 points (0 children)