Show HN: CXXStateTree – A modern C++ library for hierarchical state machines

(github.com)

48 points by zigrazor 9 days ago

36 comments

Hi HN!

I've built [CXXStateTree](https://github.com/ZigRazor/CXXStateTree), a modern C++ header-only library to create hierarchical state machines with clean, intuitive APIs.

It supports: - Deeply nested states - Entry/exit handlers - State transitions with guards and actions - Asynchronous transitions with `co_await` (C++20 coroutines) - Optional runtime type identification for flexibility

It's ideal for complex control logic, embedded systems, games, robotics, and anywhere you'd use a finite state machine.

I’d love feedback, use cases, or contributions from the community!

Repo: https://github.com/ZigRazor/CXXStateTree

jeffreygoesto 9 days ago

Nice and compact. I only wound have two nitpicks:

The Readme sais "zero heap allocations" but the code uses list and unordered map and moves, did you mean "zero allocations after state tree building"?

Also for embedded it would be useful to separate all in/out, dot export etc. to a second library that you can omit on small targets.

  • zigrazor 9 days ago

    yes, it means "zero allocations after state tree building". Thank you for the suggestions, I think we could separate target with compilation switch. If you want you can open an issue on the repo. Thank you so much

    • rpaddock 5 days ago

      In some Embedded areas where safety is of high concern following the Motor Industry Software Reliability Association (MISRA) guidelines is a requirement.

      There may be no heap at all and memory must be pre-allocated at system initialization. Otherwise CXXStateTree sounds like it could be very useful in my Embedded devices, which rarely have enough Flash or RAM space, which is the nature of the work.

      https://misra.org.uk

      • zigrazor 5 days ago

        I think it is possible to create a version with 0 heap allocation, try to open an issue with this feature request, we can see how to that togheter

wangii 6 days ago

how is it better than https://github.com/boost-ext/sml ?

there are about 1 million c++ state machines, and sml happens to be the best, or one of them. how does yours differentiate?

  • canyp 6 days ago

    I was about to complain about the use of strings in both libraries, both for the lack of type safety as well as the possible runtime allocation, but then I looked at the assembly for the sml example and there are no strings in the binary other than the obvious "send" one.

    What exactly happened there? It looks like make_transition_table() is doing some serious magic. Or are the state transitions evaluated at compile-time given that there is no input in the example, and then the transition table gets compiled out?

    Anyway, I think it would help OP's library to have some assembly output in the readme as well.

dgan 6 days ago

i am by no means a C++ expert, but isn't "pragma once" frowned upon?

  • kookamamie 6 days ago

    No, it is the way. Edit: no one has time for inventing unique names for include guards.

    • hdhdjd 6 days ago

      Does anyone write those by hand anyway in any kind of project the size where it would matter?

      #pragma once is broken by design

      • quietbritishjim 6 days ago

        > Does anyone write those by hand anyway in any kind of project the size where it would matter?

        I think you're suggesting that you don't need to make up the names for include guards because all tools / IDEs for C++ write them for you automatically anyway. But that isn't my experience. Many IDEs don't write include guards for you automatically ... because everybody uses #pragma once already.

        > #pragma once is broken by design

        I think you're referring to the historical problem with #pragma once, which is that it can be hard for the compiler to identify what is really the same file (and therefore shouldn't be included a second time). If you hard link to the same file, or soft link to it, is it the same? What if the same file is mapped to two different mount points? What if genuinely different files have the same contents (e.g., because the same library is included from two different installation paths)? In practice, soft/hard links to the same file are easily detectable, and anything more obscure indicates such a weird problem with your setup that you surely have bigger issues. #pragma once is fine.

        (Historically, it also had the benefit that compilers would know not to even re-read the header, whereas with traditional include guards they would need to re-include the file (e.g. in case the whole file is not wrapped in the #ifdef, or in case something else has undefined it since) only to then discard the contents. I've even seen coding guidelines requiring external include guards wrapped around every use of headers with #include <...>. Yuck! But modern compilers can work out when include guards are meant to mean that so today that difference probably no longer exists.)

      • bogwog 6 days ago

        I don't understand what you're saying here. #pragma once does the job that include guards used to do, but with less work, and in a less error prone way. How is it broken, and how is the size of a project relevant?

      • jcelerier 6 days ago

        Even if you don't write header guards by hand you get issues. The amount of time I got bitten by someone naming a file "widget.h" or "utils.hpp" three levels of libraries down with the corresponding #ifndef WIDGET_H which broke the build in incredibly mysterious ways...

        https://github.com/search?q=ifndef+WIDGET_H&type=code

        • tom_ 5 days ago

          I use a guid. (A different one each time, of course.)

    • motorest 6 days ago

      > No, it is the way.

      No, this is completely wrong. Pragma once is non-standard compiler directive. It might be supported by some compilers such as msvc but technically it is not even C++.

      There are only two options: include guards, and modules.

      • spacechild1 6 days ago

        Yes, it is non-standard, but I don't know any compiler that does not support it.

  • alt187 6 days ago

    All terrestrial compilers support `#pragma once`.

    In the C++ community (as lots of other things are), rejecting `#pragma once` is a long-standing tradition of worshipping the decaying body of prehistoric compilers for

    It's unclear what benefits this approach has achieved, but don't disturb it, or else.

  • benreesman 6 days ago

    You'll see a fairly even split amongst S-tier, "possibly headed for standardization" level libraries. I'd say there's a skew for `#ifndef` in projects that are more "aspires to the standard library" and for `#pragma once` in projects that are more focused on like a very specific vertical.

    `#pragma once` seems to be far preferred for internal code, there's an argument for being strictly conforming if you're putting out a library. I've converted stuff to `#ifndef` before sharing it, but I think heavy C++ people usually type `#pragma once` in the privacy of their own little repository.

    - `spdlog`: `#pragma once` https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/as...

    - `absl`: `#ifndef` https://github.com/abseil/abseil-cpp/blob/master/absl/base/a...

    - `zpp_bits`: `#ifndef` https://github.com/eyalz800/zpp_bits/blob/main/zpp_bits.h

    - `stringzilla` `#ifndef` https://github.com/ashvardanian/StringZilla/blob/main/includ...