From fb29d8c9daee569a2f4a99114260b9017a1d56c7 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sun, 30 Aug 2020 01:55:54 -0400 Subject: [PATCH] Add `noexcept` and `volatile` --- _posts/2020-08-05-static-polymorphism.md | 186 ++++++++++++++++++++++- 1 file changed, 179 insertions(+), 7 deletions(-) diff --git a/_posts/2020-08-05-static-polymorphism.md b/_posts/2020-08-05-static-polymorphism.md index b1ad58f..f2ecb4e 100644 --- a/_posts/2020-08-05-static-polymorphism.md +++ b/_posts/2020-08-05-static-polymorphism.md @@ -14,13 +14,15 @@ useful because both languages are "system." 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. +Rust's trait system is more thorough (need a better way to explain that), which allows for trait +markers, auto-deriving, arbitrary self. # Simple Example Accept parameter types, return known type. Also needs to be generic over parameter types. +Should make a quick note that C++ doesn't allow + # Generic return Same name and parameter signature, but return different types - `AsRef` @@ -113,7 +115,7 @@ public: # Method Qualifiers -Rust allows declaring immutable, mutable, and consumed arguments (including `self`). +Rust allows declaring immutable or mutable. C++ can use `const_cast` to assert "constness" of `this`: @@ -254,11 +256,174 @@ int main() { ``` Rust is much simpler about all this - the signature for a trait implementation must _exactly_ match -a trait definition. +a trait definition. Actual usage rules may be weird (what happens with a mut reference +`#[derive(Copy)]` struct when a function takes immutable by value?), but the polymorphic side stays +consistent. -C++ also has way more qualifiers - `noexcept`, `override`, `volatile`, but I can't find a way to -require those qualifiers being present. In contrast Rust doesn't have exceptions, doesn't have -inheritance, and uses `unsafe` to handle `volatile`, so doesn't need to care about these qualifiers. +Can also use `noexcept` qualifier. Not sure why this has issues: + +```c++ +#include +#include + +template +concept NoExceptMethod = requires (T a) { + { noexcept(a.method()) } -> std::same_as; +}; + +class NoExcept { +public: + std::uint64_t method() { + return 42; + } +}; + +void f(NoExceptMethod auto a) {} + +int main() { + NoExcept x{}; + + f(x); +} +``` + +Or why this is allowable: + +```c++ +#include +#include + +template +concept NoExceptMethod = requires (T a) { + { a.method() } -> std::same_as; + noexcept(a.method()); +}; + +class NoExcept { +public: + std::uint64_t method() { + return 42; + } +}; + +void f(NoExceptMethod auto a) {} + +int main() { + NoExcept x{}; + + f(x); +} +``` + +Turns out this is the way to do it: + +```c++ +#include +#include + +template +concept NoExceptMethod = requires (T a) { + { a.method() } noexcept -> std::same_as; +}; + +class NoExcept { +public: + std::uint64_t method() noexcept { + return 42; + } +}; + +void f(NoExceptMethod auto a) {} + +int main() { + NoExcept x{}; + + f(x); +} +``` + +But this doesn't compile? + +```c++ +#include +#include + +template +concept NoExceptMethod = requires (T a) { + // Note that we simply replaced `noexcept` with `const` + { a.method() } const -> std::same_as; +}; + +class NoExcept { +public: + // Note that we simply replaced `noexcept` with `const` + std::uint64_t method() const { + return 42; + } +}; + +void f(NoExceptMethod auto a) {} + +int main() { + NoExcept x{}; + + f(x); +} +``` + +```text +:6:19: error: expected ';' before 'const' + 6 | { a.method() } const -> std::same_as; + | ^~~~~~ + | ; +``` + +In general: exceptions add an orthogonal dimension of complexity on top of `const` because of how +difficult it is to deduce `noexcept` in practice. See also +http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1667r0.html + +Also, concepts getting so hard to understand that we write test cases: +https://andreasfertig.blog/2020/08/cpp20-concepts-testing-constrained-functions/ + +And for handling `volatile`: + +```c++ +#include +#include + +template +concept VolatileMethod = requires(volatile T a) { + { a.method() } -> std::same_as; +}; + +class Volatile { +public: + std::uint64_t method() volatile { + return 42; + } +}; + +void f(VolatileMethod auto a) { + a.method(); +} + +int main() { + Volatile x{}; + + f(x); +} +``` + +Though the compiler nicely warns us that we shouldn't do this: + +```text +:5:46: warning: 'volatile'-qualified parameter is deprecated [-Wvolatile] + 5 | concept VolatileMethod = requires(volatile T a) { + | ~~~~~~~~~~~^ +``` + +C++ also has `override`, but doesn't make much sense to impose that as a requirement; inheritance +and concepts are orthogonal systems. # Implement methods on remote types @@ -401,6 +566,13 @@ error message for `decltype()` is actually much nicer than the `static_assert`.. [type traits](http://en.cppreference.com/w/cpp/experimental/is_detected) to fix those issues, but mostly please just use concepts. +# Templated splatter + +Rust can't handle arbitrary numbers of template parameters. Can use macros, but I should investigate +`typename...` types. + +Common pattern to implement + # Potentially excluded Some ideas related to traits, but that I'm not sure sufficiently fit the theme. May be worth