mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 18:10:32 -05:00 
			
		
		
		
	Further reorganization
This commit is contained in:
		@ -7,8 +7,15 @@ tags: [python]
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Other languages have done similar things (interfaces in Java), but think the Rust comparison is
 | 
					Other languages have done similar things (interfaces in Java), but think the Rust comparison is
 | 
				
			||||||
useful because both languages are "system." Mostly looking at how static polymorphism is implemented
 | 
					useful because both languages are "system."
 | 
				
			||||||
in C++ and Rust, but also some comparisons to Rust behavior not strictly related to polymorphism.
 | 
					
 | 
				
			||||||
 | 
					# System Differences
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Worth noting differences in goals: polymorphism in C++ is only duck typing. Means that static
 | 
				
			||||||
 | 
					polymorphism happens separate from visibility, overloading, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Rust's trait system is different (need a better way to explain that) which allows for trait markers,
 | 
				
			||||||
 | 
					auto-deriving, arbitrary self.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Simple Example
 | 
					# Simple Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -26,10 +33,11 @@ Same name and parameter signature, but return different types - `AsRef`
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Rust has some types named by the compiler, but inaccessible in traits; can't return `impl SomeTrait`
 | 
					Rust has some types named by the compiler, but inaccessible in traits; can't return `impl SomeTrait`
 | 
				
			||||||
from traits. Can return `impl Future` from free functions and structs, but traits can't use
 | 
					from traits. Can return `impl Future` from free functions and structs, but traits can't use
 | 
				
			||||||
compiler-generated types (associated types still need to name the type). Can have traits return
 | 
					compiler-generated types (associated types still need to name the type).
 | 
				
			||||||
references (and use vtable, so no longer statically polymorphic), but typically get into all sorts
 | 
					
 | 
				
			||||||
of lifetime issues. Can also use `Box` trait objects to avoid lifetime issues, but again, uses
 | 
					Can have traits return references (`&dyn Trait`), but uses vtable (so no longer statically
 | 
				
			||||||
vtable.
 | 
					polymorphic), and very likely get into all sorts of lifetime issues. Can use `Box<dyn Trait>` trait
 | 
				
			||||||
 | 
					objects to avoid lifetime issues, but again, uses vtable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
C++ doesn't appear to have the same restrictions, mostly because the "contract" is just duck typing.
 | 
					C++ doesn't appear to have the same restrictions, mostly because the "contract" is just duck typing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,126 +45,70 @@ C++ doesn't appear to have the same restrictions, mostly because the "contract"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Shouldn't be too hard - `T::some_method()` should be compilable.
 | 
					Shouldn't be too hard - `T::some_method()` should be compilable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Arbitrary `self`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Forms the basis for Rust's async system, but used very rarely aside from that.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[`std::enable_shared_from_this`](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`enable_unique_from_this` doesn't make a whole lot of sense, but Rust can do it:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```rust
 | 
					 | 
				
			||||||
struct MyStruct {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl MyStruct {
 | 
					 | 
				
			||||||
    fn my_function(self: &Box<Self>) {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn main() {
 | 
					 | 
				
			||||||
    let unboxed = MyStruct {};
 | 
					 | 
				
			||||||
    // error[E0599]: no method named `my_function` found for struct `MyStruct` in the current scope
 | 
					 | 
				
			||||||
    // unboxed.my_function();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let boxed = Box::new(MyStruct {});
 | 
					 | 
				
			||||||
    boxed.my_function();
 | 
					 | 
				
			||||||
    boxed.my_function();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Interestingly enough, can't bind `static` version using equality:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```c++
 | 
					 | 
				
			||||||
#include <iterator>
 | 
					 | 
				
			||||||
#include <vector>
 | 
					 | 
				
			||||||
#include <concepts>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
std::uint64_t free_get_value() {
 | 
					 | 
				
			||||||
    return 24;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MyClass {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    // <source>:11:47: error: invalid pure specifier (only '= 0' is allowed) before ';' token
 | 
					 | 
				
			||||||
    std::uint64_t get_value() = free_get_value;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int main() {
 | 
					 | 
				
			||||||
    auto x = MyClass {};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Turns out the purpose of `enable_shared_from_this` is so that you can create new shared instances of
 | 
					 | 
				
			||||||
yourself from within yourself, it doesn't have anything to do with enabling extra functionality
 | 
					 | 
				
			||||||
depending on whether you're owned by a shared pointer. _At best_, you could have other runtime
 | 
					 | 
				
			||||||
checks to see if you're owned exclusively, or as part of some other smart pointer, but the type
 | 
					 | 
				
			||||||
system can't enforce that. And if you're _not_ owned by that smart pointer, what then? Exceptions?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UFCS would be able to help with this - define new methods like:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```c++
 | 
					 | 
				
			||||||
template<>
 | 
					 | 
				
			||||||
void do_a_thing(std::unique_ptr<MyType> value) {}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
In this case, the extension is actually on `unique_ptr`, but the overload resolution applies only to
 | 
					 | 
				
			||||||
pointers of `MyType`. Note that `shared_ptr` and others seem to work by overloading `operator ->` to
 | 
					 | 
				
			||||||
proxy function calls to the delegates; you could inherit `std::shared_ptr` and specialize the
 | 
					 | 
				
			||||||
template to add methods for specific classes I guess? But it's still inheriting `shared_ptr`, you
 | 
					 | 
				
			||||||
can't define things directly on it.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Generally, "you can just use free functions" seems like a shoddy explanation. We could standardize
 | 
					 | 
				
			||||||
overload `MyClass_init` as a constructor and function similar to C, etc., but the language is
 | 
					 | 
				
			||||||
designed to assist us so we don't have to do crap like that. I do hope UFCS becomes a thing.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
That said, it is interesting that for Rust, arbitrary self can be replaced with traits:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```rust
 | 
					 | 
				
			||||||
trait MyTrait {
 | 
					 | 
				
			||||||
    fn my_function(&self);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl MyTrait for Box<MyStruct> {
 | 
					 | 
				
			||||||
    fn my_function(&self) {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Just have to make sure that `MyTrait` is in scope all the time, and that's not fun. Ultimately, Rust
 | 
					 | 
				
			||||||
kinda already has UFCS. It's only "kinda" because you have to bring it in scope, and it's
 | 
					 | 
				
			||||||
potentially unclear when it's being used (extension traits), but it does get the basic job done.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Default implementation
 | 
					# Default implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
First: example of same name, different arguments. Not possible in Rust.
 | 
					First: example of same name, different arguments. Not possible in Rust.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Can you bind a free function in a non-static way? Pseudocode:
 | 
					```rust
 | 
				
			||||||
 | 
					trait MyTrait {
 | 
				
			||||||
 | 
					    // This is illegal in Rust, even though name-mangling is unique:
 | 
				
			||||||
 | 
					    // fn method(&self, value: usize) -> usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Works if you rename the method, but is a pain to type:
 | 
				
			||||||
 | 
					    fn method_with_options(&self, value: usize) -> usize;
 | 
				
			||||||
 | 
					    fn method(&self) -> usize {
 | 
				
			||||||
 | 
					        self.method_with_options(42);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MyStruct {}
 | 
				
			||||||
 | 
					impl MyTrait for MyStruct {
 | 
				
			||||||
 | 
					    fn method_with_options(&self, value: usize) -> usize {
 | 
				
			||||||
 | 
					        println!("{}", value);
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Second: example of same name, different arguments, but can't provide default implementation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```c++
 | 
					```c++
 | 
				
			||||||
template <typename T>
 | 
					template <typename T>
 | 
				
			||||||
concept DoMethod = requires (T a) {
 | 
					concept MyTrait = requires (T a) {
 | 
				
			||||||
    { a.do_method(std::declval<std::uint64_t>() } -> std::same_as<std::uint64_t>;
 | 
					    { a.method(declval<std::size_t>()) } -> std::same_as<std::size_t>,
 | 
				
			||||||
    { a.do_method() } -> std::same_as<std::uint64_t>;
 | 
					    { a.method() } -> std::same_as<std::size_t>,
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template<typename T> requires DoMethod<T>
 | 
					 | 
				
			||||||
std::uint64_t free_do_method(T& a) {
 | 
					 | 
				
			||||||
    a.do_method(0);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Each class must implement both `method` signatures.
 | 
				
			||||||
class MyClass {
 | 
					class MyClass {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    std::uint64_t do_method(std::uint64_t value) {
 | 
					    std::size_t method(std::size_t value) {
 | 
				
			||||||
        return value * 2;
 | 
					        std::cout << value << std::endl;
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Because the free function still needs a "this" reference (unlike Javascript which has a
 | 
					    std::size_t method() {
 | 
				
			||||||
    // floating `this`), we can't bind as `std::uint64_t do_method() = free_do_method`
 | 
					        return method(42);
 | 
				
			||||||
    // Also can't do it because it's a syntax error; can only use `= 0` to indicate pure virtual.
 | 
					 | 
				
			||||||
    std::uint64_t do_method() {
 | 
					 | 
				
			||||||
        return free_do_method(this);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Can write free functions as the default and then call explicitly, but for trivial
 | 
				
			||||||
 | 
					// implementations (replacing defaults) it's not likely to be worth it.
 | 
				
			||||||
 | 
					auto method_default_(auto MyTrait this) std::size_t {
 | 
				
			||||||
 | 
					    return this.method(42);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MyClassDefault {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    std::size_t method(std::size_t value) {
 | 
				
			||||||
 | 
					        std::cout << value << std::endl;
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::size_t method() {
 | 
				
			||||||
 | 
					        return method_default_(this);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Require concept methods to take `const this`?
 | 
					# Require concept methods to take `const this`?
 | 
				
			||||||
@ -165,8 +117,9 @@ public:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`is_const` could be used to declare the entire class is const, but don't think you could require
 | 
					`is_const` could be used to declare the class is const for an entire concept, but don't think you
 | 
				
			||||||
const-ness for only certain methods. Can use `const_cast` to assert "constness" though:
 | 
					could require const-ness for only certain methods. Can use `const_cast` to assert "constness"
 | 
				
			||||||
 | 
					though:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```c++
 | 
					```c++
 | 
				
			||||||
#include <concepts>
 | 
					#include <concepts>
 | 
				
			||||||
@ -294,8 +247,167 @@ Alternately, `ClientExt: AnotherTrait` implementations where the default `Client
 | 
				
			|||||||
is used. To do this, Rust compiles the entire crate as a single translation unit, and the orphan
 | 
					is used. To do this, Rust compiles the entire crate as a single translation unit, and the orphan
 | 
				
			||||||
rule.
 | 
					rule.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Rust can do one thing special though - can run methods on literals - `42.my_method()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Checking a type fulfills the concept
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					With concepts, you find out that there's an issue only when you attempt to use it. Traits in Rust
 | 
				
			||||||
 | 
					will let you know during implementation that something is wrong (there's a local error).  
 | 
				
			||||||
 | 
					https://www.ecorax.net/as-above-so-below-1/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Can use `static_assert` to kinda make sure a contract is fulfilled:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```c++
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <type_traits>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename T>
 | 
				
			||||||
 | 
					constexpr bool has_method = std::is_same_v<decltype(std::declval<T>().method()), std::uint64_t>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WithMethod {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    std::uint64_t method() { return 0; }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(has_method<WithMethod>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WithoutMethod {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// <source>: In instantiation of 'constexpr const bool has_method<WithoutMethod>':
 | 
				
			||||||
 | 
					// <source>:16:16:   required from here
 | 
				
			||||||
 | 
					// <source>:5:71: error: 'class WithoutMethod' has no member named 'method'
 | 
				
			||||||
 | 
					//     5 | constexpr bool has_method = std::is_same_v<decltype(std::declval<T>().method()), std::uint64_t>;
 | 
				
			||||||
 | 
					//       |                                                     ~~~~~~~~~~~~~~~~~~^~~~~~
 | 
				
			||||||
 | 
					// <source>:16:15: error: non-constant condition for static assertion
 | 
				
			||||||
 | 
					//    16 | static_assert(has_method<WithoutMethod>);
 | 
				
			||||||
 | 
					//       |
 | 
				
			||||||
 | 
					static_assert(has_method<WithoutMethod>);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We'd rather the example fail the static assert, rather than have an error on the `decltype`, but it
 | 
				
			||||||
 | 
					does get the job done; we're told explicitly that `WithoutMethod` has no member `method`, so the
 | 
				
			||||||
 | 
					error message for `decltype()` is actually much nicer than the `static_assert`.. Can use
 | 
				
			||||||
 | 
					[custom SFINAE](https://stackoverflow.com/a/257382) or
 | 
				
			||||||
 | 
					[experimental](https://stackoverflow.com/a/22014784)
 | 
				
			||||||
 | 
					[type traits](http://en.cppreference.com/w/cpp/experimental/is_detected) to fix those issues, but
 | 
				
			||||||
 | 
					mostly please just use concepts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Potentially excluded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Some ideas related to traits, but that I'm not sure sufficiently fit the theme. May be worth
 | 
				
			||||||
 | 
					investigating in a future post?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Visibility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Worth acknowledging that C++ can do interesting things with `protected`, `friend`, and others, that
 | 
				
			||||||
 | 
					Rust can't. However, Rust can limit trait implementations to current crate ("sealed traits"), where
 | 
				
			||||||
 | 
					C++ concepts are purely duck typing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Move/consume `self` as opposed to `&self`?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Not exactly polymorphism, but is a significant feature of Rust trait system. Is there a way to force
 | 
				
			||||||
 | 
					`std::move(object).method()`? C++ can still use objects after movement makes them invalid, so not
 | 
				
			||||||
 | 
					sure that it makes conceptual sense - it's your job to prevent use-after-move, not the compiler's.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Automatic markers?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Alternately, conditional inheritance based on templates?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Arbitrary `self`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Handled as part of section on `impl Trait` for remote type, not sure this needs it's own section.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Forms the basis for Rust's async system, but used very rarely aside from that.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[`std::enable_shared_from_this`](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`enable_unique_from_this` doesn't make a whole lot of sense, but Rust can do it:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```rust
 | 
				
			||||||
 | 
					struct MyStruct {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl MyStruct {
 | 
				
			||||||
 | 
					    fn my_function(self: &Box<Self>) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let unboxed = MyStruct {};
 | 
				
			||||||
 | 
					    // error[E0599]: no method named `my_function` found for struct `MyStruct` in the current scope
 | 
				
			||||||
 | 
					    // unboxed.my_function();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let boxed = Box::new(MyStruct {});
 | 
				
			||||||
 | 
					    boxed.my_function();
 | 
				
			||||||
 | 
					    boxed.my_function();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Interestingly enough, can't bind `static` version using equality:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```c++
 | 
				
			||||||
 | 
					#include <iterator>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <concepts>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::uint64_t free_get_value() {
 | 
				
			||||||
 | 
					    return 24;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MyClass {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    // <source>:11:47: error: invalid pure specifier (only '= 0' is allowed) before ';' token
 | 
				
			||||||
 | 
					    std::uint64_t get_value() = free_get_value;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main() {
 | 
				
			||||||
 | 
					    auto x = MyClass {};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Turns out the purpose of `enable_shared_from_this` is so that you can create new shared instances of
 | 
				
			||||||
 | 
					yourself from within yourself, it doesn't have anything to do with enabling extra functionality
 | 
				
			||||||
 | 
					depending on whether you're owned by a shared pointer. _At best_, you could have other runtime
 | 
				
			||||||
 | 
					checks to see if you're owned exclusively, or as part of some other smart pointer, but the type
 | 
				
			||||||
 | 
					system can't enforce that. And if you're _not_ owned by that smart pointer, what then? Exceptions?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UFCS would be able to help with this - define new methods like:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```c++
 | 
				
			||||||
 | 
					template<>
 | 
				
			||||||
 | 
					void do_a_thing(std::unique_ptr<MyType> value) {}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In this case, the extension is actually on `unique_ptr`, but the overload resolution applies only to
 | 
				
			||||||
 | 
					pointers of `MyType`. Note that `shared_ptr` and others seem to work by overloading `operator ->` to
 | 
				
			||||||
 | 
					proxy function calls to the delegates; you could inherit `std::shared_ptr` and specialize the
 | 
				
			||||||
 | 
					template to add methods for specific classes I guess? But it's still inheriting `shared_ptr`, you
 | 
				
			||||||
 | 
					can't define things directly on it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Generally, "you can just use free functions" seems like a shoddy explanation. We could standardize
 | 
				
			||||||
 | 
					overload `MyClass_init` as a constructor and function similar to C, etc., but the language is
 | 
				
			||||||
 | 
					designed to assist us so we don't have to do crap like that. I do hope UFCS becomes a thing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					That said, it is interesting that for Rust, arbitrary self can be replaced with traits:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```rust
 | 
				
			||||||
 | 
					trait MyTrait {
 | 
				
			||||||
 | 
					    fn my_function(&self);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl MyTrait for Box<MyStruct> {
 | 
				
			||||||
 | 
					    fn my_function(&self) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Just have to make sure that `MyTrait` is in scope all the time, and that's not fun. Ultimately, Rust
 | 
				
			||||||
 | 
					kinda already has UFCS. It's only "kinda" because you have to bring it in scope, and it's
 | 
				
			||||||
 | 
					potentially unclear when it's being used (extension traits), but it does get the basic job done.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Trait objects as arguments
 | 
					# Trait objects as arguments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Handled as part of `decltype` and compiler-named types, not sure it needs it's own section.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```rust
 | 
					```rust
 | 
				
			||||||
trait MyTrait {
 | 
					trait MyTrait {
 | 
				
			||||||
    fn some_method(&self);
 | 
					    fn some_method(&self);
 | 
				
			||||||
@ -357,67 +469,3 @@ disorienting.
 | 
				
			|||||||
`dyn Trait` seems to be used in Rust mostly for type erasure - `Box<Pin<dyn Future>>` for example,
 | 
					`dyn Trait` seems to be used in Rust mostly for type erasure - `Box<Pin<dyn Future>>` for example,
 | 
				
			||||||
but is generally fairly rare, and C++ probably doesn't suffer for not having it. Can use inheritance
 | 
					but is generally fairly rare, and C++ probably doesn't suffer for not having it. Can use inheritance
 | 
				
			||||||
to force virtual if truly necessary, but not sure why you'd need that.
 | 
					to force virtual if truly necessary, but not sure why you'd need that.
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Checking a type fulfills the concept
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
With concepts, you find out that there's an issue only when you attempt to use it. Traits in Rust
 | 
					 | 
				
			||||||
will let you know during implementation that something is wrong (there's a local error).  
 | 
					 | 
				
			||||||
https://www.ecorax.net/as-above-so-below-1/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Can use `static_assert` to kinda make sure a contract is fulfilled:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```c++
 | 
					 | 
				
			||||||
#include <cstdint>
 | 
					 | 
				
			||||||
#include <type_traits>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
template<typename T>
 | 
					 | 
				
			||||||
constexpr bool has_method = std::is_same_v<decltype(std::declval<T>().method()), std::uint64_t>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class WithMethod {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    std::uint64_t method() { return 0; }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static_assert(has_method<WithMethod>);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class WithoutMethod {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// <source>: In instantiation of 'constexpr const bool has_method<WithoutMethod>':
 | 
					 | 
				
			||||||
// <source>:16:16:   required from here
 | 
					 | 
				
			||||||
// <source>:5:71: error: 'class WithoutMethod' has no member named 'method'
 | 
					 | 
				
			||||||
//     5 | constexpr bool has_method = std::is_same_v<decltype(std::declval<T>().method()), std::uint64_t>;
 | 
					 | 
				
			||||||
//       |                                                     ~~~~~~~~~~~~~~~~~~^~~~~~
 | 
					 | 
				
			||||||
// <source>:16:15: error: non-constant condition for static assertion
 | 
					 | 
				
			||||||
//    16 | static_assert(has_method<WithoutMethod>);
 | 
					 | 
				
			||||||
//       |
 | 
					 | 
				
			||||||
static_assert(has_method<WithoutMethod>);
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
We'd rather the example fail the static assert, rather than have an error on the `decltype`, but it
 | 
					 | 
				
			||||||
does get the job done; we're told explicitly that `WithoutMethod` has no member `method`, so the
 | 
					 | 
				
			||||||
error message for `decltype()` is actually much nicer than the `static_assert`.. Can use
 | 
					 | 
				
			||||||
[custom SFINAE](https://stackoverflow.com/a/257382) or
 | 
					 | 
				
			||||||
[experimental](https://stackoverflow.com/a/22014784)
 | 
					 | 
				
			||||||
[type traits](http://en.cppreference.com/w/cpp/experimental/is_detected) to fix those issues, but
 | 
					 | 
				
			||||||
mostly please just use concepts.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Visibility
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Worth acknowledging that C++ can do interesting things with `protected`, `friend`, and others, that
 | 
					 | 
				
			||||||
Rust can't. However, Rust can limit trait implementations to current crate ("sealed traits"), where
 | 
					 | 
				
			||||||
C++ concepts are purely duck typing.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Potentially excluded
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Some ideas related to traits, but that I'm not sure sufficiently fit the theme. May be worth
 | 
					 | 
				
			||||||
investigating in a future post?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Move/consume `self` as opposed to `&self`?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Not exactly polymorphism, but is a significant feature of Rust trait system. Is there a way to force
 | 
					 | 
				
			||||||
`std::move(object).method()`? C++ can still use objects after movement makes them invalid, so not
 | 
					 | 
				
			||||||
sure that it makes conceptual sense - it's your job to prevent use-after-move, not the compiler's.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Automatic markers?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Alternately, conditional inheritance based on templates?
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user