6 ms·
C++20 supports enums as non-type template parameters, so I think it'd be possible to do it with enums there. Something like: enum class Color{Green, Yellow
by logicchains 7y ago
C++20 supports enums as non-type template parameters, so I think it'd be possible to do it with enums there. Something like:
enum class Color{Green, Yellow, Red};
template<Color C>
struct State{};
auto newState() -> State<Color::Green> {...};
auto next(State<Color::Green>) -> State<Color::Yellow> {...}
auto next(State<Color::Yellow>) -> State<Color::Red> {...}
auto next(State<Color::Red>) -> State<Color::Green> {...}
int main(){
const auto state = newState(); // Green
const auto state = next(state); // Yellow
const auto state = next(state); // Red
const auto state = next(state); // Green
const auto state = next(state); // Yellow
}
- alvarelle 7y agoIn C++ you can't hide a variable with another of the same name in the same scope. So you'd want to give different names to all the 'state' variables.
- Arnavion 7y agoRust will also support it one day, as part of the const generics feature. Partial support is there behind a feature flag. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=7cf04999dd6d093fc34da5c022a04b8f https://play.rust-lang.org/?version=nightly&mode=debug&editi... #![feature(const_generics)] #[derive(PartialEq, Eq)] // enums used as const generics must be Eq enum Color { Green, Yellow, Red } struct State<const COLOR: Color>; impl State<{ Color::Green }> { fn next(self) -> State<{ Color::Yellow }> { State } } impl std::fmt::Debug for State<{ Color::Green }> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("green") } } impl State<{ Color::Yellow }> { fn next(self) -> State<{ Color::Red }> { State } } impl std::fmt::Debug for State<{ Color::Yellow }> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("yellow") } } impl State<{ Color::Red }> { fn next(self) -> State<{ Color::Green }> { State } } impl std::fmt::Debug for State<{ Color::Red }> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("red") } } impl State<{ Color::Green }> { fn new() -> Self { State } } fn main() { let state = dbg!(State::<{ Color::Green }>::new()); let state = dbg!(state.next()); let state = dbg!(state.next()); let state = dbg!(state.next()); } Output: [src/main.rs:20] State::<{ Color::Green }>::new() = green [src/main.rs:21] state.next() = yellow [src/main.rs:22] state.next() = red [src/main.rs:23] state.next() = green
- DougBTX 7y agoAre the braces around the const expressions planned to be optional in future? I'm wondering if this will work: impl State<Color::Green> { fn next(self) -> State<Color::Yellow> { ... } }
- Arnavion 7y agoI believe they're intentional, to disambiguate from `mod Color { struct Green; }`, and because they can be arbitrarily complex expressions.
- grenoire 7y agoCurrently the compiler gives "not a type" when the brackets are removed; I agree that it might be to disambiguate, but it's confusing and redundant syntax. I expect { } to be used strictly for blocks in Rust.
- hobofan 7y agoIt's a block though, isn't it? Color::Green is an enum variant while { Color::Green } is a (constant) block (expression) that evaluates to an instance which is used as a generic type parameter. The difference might be more easily understandable if we look at an enum variant that holds a value, where the syntactic differences between variant and instance constructor are more clearly visible. Color::RGB(u64, u64, u64) vs { Color::RGB(10, 20, 30) }
- DougBTX 7y agoHm, the RFC says the braces are needed if it is not an "identity expression" with the examples: const X: usize = 7; let x: RectangularArray<i32, 2, 4>; let y: RectangularArray<i32, X, {2 * 2}>; So I'm still not sure if Color::Green is an identity expression, they say: > Identity expression: An expression which cannot be evaluated further except by substituting it with names in scope. This includes all literals as well all idents So... maybe? Depends on whether Color::Green is an ident or not. At the very least I'd expect this to work, but it doesn't yet: use Color::*; impl State<Green> { ... }
- jcelerier 7y ago> C++20 supports enums as non-type template parameters, so I think it'd be possible to do it with enums there. Something like: Even c++98 supported that already : https://gcc.godbolt.org/z/7xma_u https://gcc.godbolt.org/z/7xma_u
- logicchains 7y agoGood point, I suppose of course it does, since enums are just ints. But C++20 adds support for enum classes (and also variants when they can be defined as value types with a defaulted operator<==>).
- jcelerier 7y ago> But C++20 adds support for enum classes It seems to work fine with `enum class` under C++11 ? your code works almost inchanged in g++ 4.7. I don't see anything here : https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter https://en.cppreference.com/w/cpp/language/template_paramete... that adds specific support for that in C++20
- logicchains 7y agoWow it does too, not sure where I got it into my head that it didn't then.
- msaltz 7y agoI made a rough attempt a little while ago to make something P-like with C++17 constructs, I think it turned out pretty nicely: https://gist.github.com/saltzm/f2acd7f656436dd63c9808ea1173807c https://gist.github.com/saltzm/f2acd7f656436dd63c9808ea11738... There’s one extra validation hop to check valid state transitions that I don’t love, but if I take that feature out then I think it’s pretty low overhead for how readable it is
- fluffything 7y agoThat example does not work, because declaring `state` multiple times creates an illegal C++ program (redeclaration of local variable - notice that this is not the case in Rust). You need to declare variables with different names: const auto state0 = newState(); const auto state1 = next(state0); const auto state2 = next(state1); const auto state3 = next(state); // TYPO -> BOOM use after move I don't think this can be implemented safely in C++ without creating a "moved from" state that terminates the program on use, because C++ does not have Affine or Linear types. That is, you can't use an `enum class`, since you can't implement move constructors and destructors for it, so you need to use a `variant` wrapper or similar: struct Color { struct Green {}; struct Red {}; struct Blue {}; struct MovedFrom {}; using data_t = variant<Green, Red, Blue, MovedFrom>; data_t data; Color(Color&& other) { data = other.data; other.data = MovedFrom{}; } //... another dozens lines of boilerplate... }; and you can't probably use variant either, since using variant would introduce yet another possible state (e.g. if an exception gets thrown..). So doing this right on C++ probably requires 100s of lines of boiler plate, it probably requires run-time state to keep track of moved-from values to enforce that states that have already been used are not used anymore, etc. At this point you might as well just write that part of your code in Rust, where `enum Color { Gree, Red, Blue }` just works and will do what you want without any run-time state. If you need to do compile-time computations, you can either use nightly and use const generics, or you can use stable Rust and write a proc macro. Both options are easier for humans to get right than the amount C++ boilerplate that's going to be required to avoid the fact that move operations are not "destructive" / affine. Another user below was arguing that they preferred to use C++ because there they don't need to use `{ }` to disambiguate const generics, yet they are apparently fine with using `var.template member_fn` to disambiguate all template method calls... I imagine many users will argue that writing all the boilerplate above is "fine" or "not a big deal". To me all this sounds like Stockholm syndrome: somebody must use C++, they have been using it for 10 years already, and having to write all these boilerplate and know all these detail nitpicks of trivia to write a trivial piece of code makes them feel clever and gives them job security. I'm not even going to read your comments so really don't bother replying if that's what you are going to talk about.
- leni536 7y ago