How to Use C++ With Zephyr
Zephyr has pretty good support for C++.
Not sure if you want to use C++ for embedded development? Check out my article C++ on Embedded Systems for more information.
Enabling C++ Support
You can enable support for compiling C++ by adding the following into prj.conf
:
CONFIG_CPP=y
You can then change main.c
to main.cpp
. Remember to update the path in the CMakeLists.txt
file also! However, you will still be missing a lot of the C++ standard library (e.g. std::functional
). To make this available, add the following to your prj.conf
:
# Adds the full C++ standard libraryCONFIG_REQUIRES_FULL_LIBCPP=y
Remember you can call C code from C++, but not the other way around (well — technically you can, but it general if you are using classes and other C++ stuff you can’t). So one migration technique is that once your main.cpp
file is working, you can upgrade some of your core application code to C++ but leave things like drivers and libraries in C. And of course, you can still access the Zephyr API (which is written in C) from your C++ code. All the Zephyr headers are wrapped in extern C
allowing you to call them easily from C++.
Choosing a C++ Standard
By default, Zephyr will use the C++11 standard. This is quite old these days, and you will likely want to use a newer standard. As of June 2025, Zephyr supports up to C++20. You can select the version you want from one of the following:1
- CONFIG_STD_CPP98
- CONFIG_STD_CPP11
- CONFIG_STD_CPP14
- CONFIG_STD_CPP17
- CONFIG_STD_CPP2A
- CONFIG_STD_CPP20
- CONFIG_STD_CPP2B
Just add the one you want with an =y
to your prj.conf
file, e.g.:
CONFIG_STD_CPP20=y # Use C++20
I didn’t notice any significant RAM or flash increase just by including the full C++ standard library, so you only pay for what you use.
You should now be able to compile the Zephyr application with C++ code. In summary, your prj.conf
should look something like this:
CONFIG_CPP=yCONFIG_REQUIRES_FULL_LIBCPP=yCONFIG_STD_CPP20=y
Logging
At some point when using C++, you are going to want to place logging calls in header files. This is true for any template code (which has to be in the header file) or for any class functions which you can’t be bothered defining in a separate .cpp
file.
You have to add LOG_MODULE_DECLARE(<module_name>);
to the beginning of any function in the header file which you want to log from.
I think Zephyr’s instance based logging is a bit of a mess and don’t really use it. This is unfortunate as it would have paired well with instances of C++ classes.
Expected
Since C++23 is not supported yet in Zephyr (as of June 2025), we can’t get std::expected
. However, TartanLlama (Sy Brand) has created a replacement implementation that is supported on C++11 and later. You can find the repository here on GitHub.
To add this dependency into your project, add the following to your CMakeLists.txt
file:
# Disable packaging for TlExpectedset(EXPECTED_BUILD_PACKAGE OFF CACHE BOOL "Disable TlExpected packaging" FORCE)set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "Disable TlExpected tests" FORCE)
include(FetchContent)FetchContent_Declare( TlExpected GIT_REPOSITORY https://github.com/TartanLlama/expected GIT_TAG v1.1.0)FetchContent_MakeAvailable(TlExpected)
# The target 'expected' (aliased as tl::expected) is now configured.# Link against the alias provided by the library.target_link_libraries(app PUBLIC tl::expected)
This uses FetchContent
to automatically download the repository and make it available to your project. EXPECTED_BUILD_PACKAGE
and EXPECTED_BUILD_TESTS
both need to be set to OFF
to prevent compiler errors since the Zephyr build environment does not support testing or packaging the library (we just want the header files).
Exceptions
Exceptions are disabled by default when you enable C++ support Zephyr. If you try and add a throw std::runtime_error("test");
to your code, you will get the following compiler error:
error: exception handling disabled, use '-fexceptions' to enable
You can enable exceptions by adding the following to your prj.conf
file:
CONFIG_CPP_EXCEPTIONS=y
You can only enable exceptions if you:
- Have enabled C++ with
CONFIG_CPP=y
. - Have not enabled
CONFIG_NEWLIB_LIBC_NANO
orCONFIG_MINIMAL_LIBCPP
.
Whether or not you want to use C++ exceptions is something you should consider carefully. If my understanding is correct, exceptions will usually use dynamic memory allocation to store the thrown exception. This will forbid exceptions if you have a rule about no dynamic memory allocation or no dynamic memory allocation after initialization.
See the C++ on Embedded Systems page for more information about the pros and cons of using C++ exceptions.
Footnotes
-
Zephyr (2022, Jan 12). Kconfig Search > CONFIG_STD_CPP [documentation]. Retrieved 2025-06-14, from https://docs.zephyrproject.org/latest/kconfig.html#!CONFIG_STD_CPP. ↩