Further refining remote types

This commit is contained in:
Bradlee Speice 2020-08-29 21:16:57 -04:00
parent e48b2f5abb
commit e24cc4c7a5

View File

@ -178,19 +178,19 @@ int main() {
| ^~~~~~ | ^~~~~~
``` ```
# Local trait implementation of remote data type? # Implement methods on remote types
AKA "extension methods". UFCS can accomplish this, and could use free functions to handle instead, Rust allows both arbitrary `self` and extension traits. Arbitrary self forms the basis of the
but having the IDE auto-complete `.<the next thing>` is exceedingly useful, as opposed to memorizing `async` system in Rust. Extension traits form basis of `futures` library. Accomplish effectively the
what functions are necessary for conversion. We're not changing what's possible, just making it same thing, but for concrete types and traits respectively.
easier for humans.
Likely requires sub-classing the remote class. Implicit conversions don't _really_ work because they UFCS would achieve the same effect, but unclear if/when it will be available:
must be defined on the remote type (not true: `operator Local` must be defined on remote, but https://dancrn.com/2020/08/02/ufcs-in-clang.html
`Local` could have a `Local(const Remote&)` implicit constructor). Could maybe use wrapper classes
that have single-arg (implicit) constructors, and get away with it as long as the wrapper knows it's Can use free functions in the meantime, but having the IDE auto-complete `.<the next thing>` is
not safe to modify the internals. That said, wrapper can only use the public interface unless exceedingly useful, as opposed to looking through all functions in a namespace.
declared friend (which is no different to Rust).
Can also sub-class or implicitly convert to a wrapper:
```c++ ```c++
#include <concepts> #include <concepts>
@ -227,27 +227,51 @@ void regular_func(LocalImpl value) {
int main() { int main() {
SomeRemoteClass x {}; SomeRemoteClass x {};
// This isn't OK because `auto` doesn't automatically convert to `LocalImpl` // This _will not_ compile because `auto` doesn't trigger the conversion to `LocalImpl`
//auto_func(x); //auto_func(x);
// This _is_ OK because we explicitly declare the class we want (`LocalImpl`) and `SomeRemoteClass` // This _will_ compile because the function signature declares a concrete class for which an
// is implicitly converted. Just so happens that `LocalImpl` implements `MyConcept`. // implicit conversion is available. It just so happens that `LocalImpl` satisfies `MyConcept`.
regular_func(x); regular_func(x);
// We could extend the conversion pattern using specializations of `LocalImpl`, or maybe use
// `std::variant` to hold different internal types, but there's still a disconnect between
// what we actually want to fulfill (`MyConcept`) and how that's implemented for remote types
// (using the `LocalImpl` wrapper and implicit conversions).
} }
``` ```
Rust makes this weird because you have to `use ClientExt` to bring the methods in scope, but the The `LocalImpl` wrapper could be extended to handle additional remote types using template
trait name might not show up because `impl ClientExt for RemoteStruct` is defined elsewhere. specialization or holding an internal `std::variant`, but that misses the point: we want to write
Alternately, `ClientExt: AnotherTrait` implementations where the default `ClientExt` implementation code that accepts anything that satisfies `MyConcept`. When we write functions that require a
is used. To do this, Rust compiles the entire crate as a single translation unit, and the orphan specific wrapper, we're being overly restrictive, and obfuscating our intentions (we don't actually
rule. care about the wrapper, it's just there for ease-of-use).
Rust can do one thing special though - can run methods on literals - `42.my_method()`. Can use some overloading/specialization tricks for ease of use:
```c++
auto some_func_(MyConcept auto value) -> void {
auto x = value.do_something();
}
auto some_func(MyConcept auto value) -> void {
some_func_(value);
}
void some_func(LocalImpl value) {
some_func_(value);
}
```
Need to be careful though:
```c++
auto some_func(MyConcept auto value) -> void {
auto x = value.do_something();
}
void some_func(LocalImpl value) {
// NOTE: This is actually a recursive call because `LocalImpl` is more specific than `auto`,
// so will overflow the stack.
// We use `some_func_` above to uniquely name the function we actually want to call.
some_func(value);
}
```
# Checking a type fulfills the concept # Checking a type fulfills the concept