- Format all code:
pixi run lint - Quick conventions: 2-space indent, camelCase functions, PascalCase classes
- C++ headers:
.hpp, sources:.cpp, member vars prefixed withm - Python bindings: snake_case functions (PEP 8), PascalCase classes
- See Quick Reference for the full summary
This document describes the code style conventions used in the DART project.
C++ headers and sources should be contained in the same subdirectory of dart/ that matches their namespace, with the extension .hpp and .cpp, respectively.
DART uses C++20 features to improve code clarity, maintainability, and performance. Use these patterns when appropriate:
Concepts for Template Constraints:
// Prefer concepts over SFINAE
template <std::floating_point T>
T normalize(T value);
// Custom concepts for domain-specific constraints
template <typename T>
concept UniformIntCompatible = std::integral<T> && !std::same_as<T, bool>;std::span for Function Parameters:
// Prefer std::span over const std::vector<T>&
void addSkeletons(std::span<const SkeletonPtr> skeletons);
// Accepts vectors, arrays, subranges without temporary allocationsSpaceship Operator for Comparisons:
// Replace 6 operators with 2
auto operator<=>(const Ptr& other) const {
return std::tie(mT1, mT2) <=> std::tie(other.mT1, other.mT2);
}
bool operator==(const Ptr& other) const = default;Branch Prediction Attributes:
// Use [[likely]] / [[unlikely]] for hot paths
if (objects.empty()) [[unlikely]] {
return false;
}
if (!result.isCollision()) [[likely]] {
return;
}When NOT to use C++20 features:
- Avoid
std::formatuntil Ubuntu 24.04 LTS (GCC 13+) - Don't replace clear index-based loops with complex range expressions
- Keep SFINAE for Eigen compile-time traits (not proper constexpr)
Modernization summary (DART 7): The codebase now assumes a C++20 baseline,
so new read-only APIs should prefer non-owning views (std::span,
std::string_view) and call sites should avoid temporary containers. Favor
std::ranges algorithms, associative contains, and std::erase(_if) when
they clarify intent. Keep modernizations behavior-preserving; avoid deep
algorithm rewrites.
DART now uses snake_case for public headers across the core C++ surface while preserving compatibility via generated PascalCase wrappers.
- Core headers (
dart/): Use snake_case for headers (e.g.,body_node.hpp,collision_group.hpp). PascalCase compatibility headers are generated by CMake for legacy includes and emit deprecation warnings. - Legacy sources (
dart/): Keep existing.cppfile names unless you are performing a targeted migration; avoid mass-renames for sources in this release. - Next-gen modules (
dart/simulation/experimental/,tests/unit/simulation/experimental/,tests/benchmark/simulation/experimental/, and dependents): Use all snake_case file names (e.g.,rigid_body.hpp,free_joint.cpp,test_multi_body.py). Build-system files such asCMakeLists.txtkeep their canonical capitalization. - Implementation details: Continue to use the
-implsuffix for template implementations (e.g.,body_node-impl.hpp,rigid_body-impl.hpp). - Tests (
tests/): Use snake_case file names withtest_prefix (e.g.,test_skeleton_state.cpp,test_collision.cpp). This avoids case-collisions on case-insensitive filesystems (macOS, Windows) and aligns with the DART 7 snake_case convention.
Rationale: The public include surface is moving to snake_case while generated PascalCase wrappers keep downstream builds working. All new code uses snake_case consistently.
- Indentation: 2 spaces
- Functions: camelCase
- Classes: PascalCase
- Enum members: PascalCase (e.g.,
enum class Mode { Disabled, Enabled };) - Member variables: Prefixed with
m(e.g.,mExampleMember) - File extensions:
.hppfor headers,.cppfor sources - File naming: snake_case for all new headers and sources in
dart/andtests/(PascalCase wrappers generated by CMake for headers only); avoids case-collisions on case-insensitive filesystems (macOS, Windows) - Header guards:
DART_NAMESPACE_CLASSNAME_HPP_ - Braces: No "cuddled" braces for functions/classes (namespaces and control flow are exceptions); control-flow bodies are automatically wrapped in braces by
pixi run lint. - Documentation: Doxygen-style comments (
///)
- New/modified code: Use camelCase names for function parameters (e.g.,
void setMass(double mass)). - Legacy code: Many functions in
dart/dynamicsstill use_paramconventions. Avoid mass renames—update these opportunistically when touching the surrounding code, and prefer camelCase for any new APIs.
Rules:
- Use two-space indentation
- Use camelCase function names
- Use PascalCase class names
- No "cuddled" braces for classes or functions!
- Control-flow bodies are automatically wrapped in braces by
pixi run lint - Header guards must include the library, namespace, and source file names
Example:
#ifndef DART_EXAMPLE_EXAMPLECLASS_HPP_
#define DART_EXAMPLE_EXAMPLECLASS_HPP_
// Place all dependency includes at the top of the file.
// Use absolute paths. Order: Local -> Library -> External -> STL.
#include "dart/common/pointers.hpp"
#include <dart/component_name/ExampleInterface.hpp>
#include <dart/component_name/ExampleOtherInterface.hpp>
#include <library/headers.hpp>
#include <stl_headers>
// Namespaces scopes should be one line each with "cuddled" braces.
namespace dart {
namespace example {
// Use the following macro (defined in dart/common/SmartPointer.hpp) to declare
// STL "smart" pointers. Pointers should be declared ahead of the class so
// that the class itself can use the pointers.
DART_COMMON_DECLARE_SHARED_WEAK(ExampleClass)
/// A required Doxygen comment description for this class. This can be extended
/// to include various useful details about the class, and can use the standard
/// Doxygen tag set to refer to other classes or documentation. It should use
/// the '///' style of block comment.
class ExampleClass : public ExampleInterface, public ExampleOtherInterface
{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW // Many classes that require Eigen will also need this macro
/// Required brief description of constructor. This will often be as simple as:
/// "Creates an instance of ExampleClass."
///
/// @param[in] foo This is an example parameter description.
/// @param[in] bar This is a longer example parameter description that needs
/// to wrap across multiple lines.
ExampleClass(std::unique_ptr<util::RNG> foo,
const Eigen::Isometry3d& bar = Eigen::Isometry3d::Identity());
ExampleClass(const ExampleClass& other);
ExampleClass(ExampleClass&& other);
ExampleClass& operator=(const ExampleClass& other);
ExampleClass& operator=(ExampleClass&& other);
// If a class should be non-copyable, it should explicitly delete the following:
ExampleClass(const ExampleClass&) = delete;
ExampleClass(ExampleClass&& other) = delete;
ExampleClass& operator=(const ExampleClass& other) = delete;
ExampleClass& operator=(ExampleClass&& other) = delete;
// Classes should explicitly declare a default virtual destructor
// if they do not declare one (unless marking a class as final).
virtual ~ExampleClass() = default;
// Documentation inherited. <-- Use this comment to indicate that the docstring of the interface method applies
int exampleInterfaceFunction() const override; // <-- Always explicitly `override` interface functions without `virtual`
/// Required brief description of method.
/// @note If a method has output parameters, they should be the last
/// arguments.
///
/// @param[in] a A description of a
/// @param[in] b A description of b
/// @param[out] out A description of out
int exampleMethod(int a, int b, int* out) const;
private:
std::unique_ptr<util::RNG> mExampleMember; // Member variables are prefixed with "m"
};
} // namespace example
} // namespace dart
// In certain cases, such as heavily templated code, implementations must be included
// in headers. In this case, a "detail" header should be created in the "./detail"
// subdirectory with the same name as the main header file, but an "-impl" suffix.
// Private declarations in this header can use a "detail" sub-namespace.
#include "dart/component_name/detail/ExampleClass-impl.hpp"
#endif // DART_EXAMPLE_EXAMPLECLASS_HPP_Rules:
- Use two-space indentation
- Use camelCase function names
- Use PascalCase class names
- No "cuddled" braces for classes or functions!
- Control-flow bodies are automatically wrapped in braces by
pixi run lint - Each function is separated by an 80 column line of "=" characters
Example:
// Includes should be at the top of the file.
// The first include in a class source file should be the matching `.hpp` header file.
#include "dart/example/ExampleClass.hpp"
#include <stl_headers>
#include <library/headers.hpp>
#include "dart/example/OtherHeaders.hpp"
// Namespace nesting is preferred to "using namespace" directives.
// Namespaces scopes should be one line each with "cuddled" braces.
namespace dart {
namespace example {
// Each function is separated by an 80 column line of "=" characters.
//==============================================================================
int ExampleClass::exampleInterfaceFunction() const
{
if (mExampleMember) {
return 3;
}
return -1;
}
//==============================================================================
int ExampleClass::exampleMethod(int a, int b, int* out) const
{
int result = a + b;
if (out) {
*out = result;
}
return result;
}
} // namespace example
} // namespace dartNever swallow exceptions silently. When catching exceptions for logging, always re-throw to propagate the error:
// CORRECT: Log and re-throw
try {
value = std::stod(str);
} catch (const std::exception& e) {
DART_ERROR("Failed to parse '{}': {}", str, e.what());
throw; // Re-throw to propagate the error
}
// WRONG: Silently swallow the exception
try {
value = std::stod(str);
} catch (const std::exception& e) {
DART_ERROR("Failed to parse '{}': {}", str, e.what());
// Missing throw! Execution continues with uninitialized data
}Rationale: Silently swallowing exceptions can lead to subtle bugs where code continues with uninitialized or partial data. Always propagate errors to the caller so they can handle them appropriately.
These guidelines are based on Herb Sutter's article. Consider looking at the full article for details.
General Rules:
- Use a by-value
std::shared_ptras a parameter if the function surely takes the shared ownership. - Use a
const std::shared_ptr&as a parameter only if you're not sure whether or not you'll take a copy and share ownership. - Use a non-const
std::shared_ptr¶meter only to modify thestd::shared_ptr. - Use
std::unique_ptranytime you want to use astd::shared_ptrbut don't need to share ownership. - Otherwise use
Object*instead, orObject&if not nullable.
All macros in DART are prefixed with DART_ to distinguish them from other identifiers:
DART_HAS_<optional_dep>: Boolean set to true when an optional dependency is detectedDART_ENABLE_<optional_feature>: Boolean set to true if the optional feature should be enabled when requirements are metDART_ENABLED_<optional_feature>: Boolean set to true if the optional feature is enabled
Use all-caps for all macro names to ensure consistency and to visually distinguish macros from other variables.
The Python bindings use different naming conventions than the C++ code to follow Python community standards:
| Element | C++ Style | Python Style | Example (C++) | Example (Python) |
|---|---|---|---|---|
| Functions | camelCase | snake_case | isIdentity() |
is_identity() |
| Classes | PascalCase | PascalCase | MyClass |
MyClass |
| Variables | snake_case | snake_case | my_variable |
my_variable |
| Constants | ALL_CAPS | ALL_CAPS | MY_CONSTANT |
MY_CONSTANT |
| Namespaces/Modules | N/A | snake_case | dart::dynamics |
dart.dynamics |
C++ code:
auto so3 = SO3();
bool is_identity = so3.isIdentity();Python code:
so3 = SO3()
is_identity = so3.is_identity()Using different naming conventions allows each API to follow its language's community standards:
- camelCase is more common for function names in the C++ community
- snake_case is more common for function names in the Python community
This makes the code more readable and easier to integrate with other projects in each ecosystem, while maintaining consistency within each language.
- Indentation: 2 spaces
- Functions: lowercase
- Variables: ALL_CAPS (except target names)
- Target variables:
target_VARIABLEformat - Quoting: Always quote singleton variables (
"${MY_VAR}"), but not lists
See CONTRIBUTING.md for detailed examples.
Use ClangFormat to automatically format C++ code:
cd build/
make check-format # Check without modifying
make format # Apply formatting| Command | Success | Failure |
|---|---|---|
pixi run lint |
No errors, check git status for changes |
Error messages in output |
make check-format |
"All files formatted correctly" | List of files needing formatting |
make format |
Files reformatted silently | N/A (always succeeds) |
- CONTRIBUTING.md - Full style guide with detailed examples
- building.md - Build instructions and CMake configuration
- python-bindings.md - Python bindings architecture