Guidelines
This document outlines the coding guidelines for developing in mfem-mgis.
These rules ensure consistency, maintainability, and robustness of the codebase.
General Rules
C++ Standard: The project uses C++20. All code must conform to this standard.
Code Style: Follow the project’s
clang-formatconfiguration (see.clang-format). Runclang-format -i $(find . -name "*xx")to format your code.Header Guards: All header files must use the standard header guard pattern:
#ifndef LIB_MFEMMGIS_<PATH>_HXX #define LIB_MFEMMGIS_<PATH>_HXX // code #endif /* LIB_MFEMMGIS_<PATH>_HXX */
Includes: Use forward declarations where possible to reduce compile-time dependencies. Include headers in the following order:
Standard library headers
Third-party library headers (e.g., MFEM, MGIS)
Project headers
Standard library headers come first, even when they are only needed in a conditional block.
Namespaces: All code must be in the
mfem_mgisnamespace or a nested namespace.Doxygen Comments: All public classes, methods, and functions must have Doxygen comments. Avoid redundant
\briefsections if they duplicate the description./*! * \brief Brief description. * * Detailed description if necessary. * * \param[in] arg: Description of the argument. * \return Description of the return value. */
Error Messages: Error messages must be clear, descriptive, and include relevant context (e.g., parameter names, expected types, or constraints).
Error Handling
The project uses a dual error handling model (see MGIS error handling documentation):
Non-throwing mode: For functions that accept a
Context.Throwing mode: For constructors or functions marked with
attributes::Throwing.
Non-throwing Mode
Never throw exceptions in functions that accept a
Context.Report errors using the
Context:bool myFunction(Context& ctx, ...) noexcept { if (error_condition) { return ctx.registerErrorMessage("descriptive error message"); } return true; }
Use
isInvalidto check for errors in returned values:auto result = someFunction(ctx, ...); if (isInvalid(result)) { // Handle error return ctx.registerErrorMessage("failed to do something"); }
Return types for non-throwing functions:
bool: For functions that return a success/failure status.std::optional<T>: For functions that may return a value or nothing.OptionalReference<T>: For functions that may return a reference or nothing.InvalidResult: A special type representing an invalid result.
Throwing Mode
Reserved for constructors or functions marked with
attributes::Throwing.Use the
raisefunction to throw exceptions:MyClass::MyClass(...) : member(throwing, ...) { if (error_condition) { raise("descriptive error message"); } }
Use the
throwingattribute to call functions that may throw:const auto value = get<int>(throwing, params, "ParameterName");
Error Propagation
To generate an exception for a function using
Contextto report errors, use a throwing handler, as follows:auto or_raise = ctx.getThrowingFailureHandler(); someFunction(ctx, ...) | or_raise;
Parameter Handling
For a detailed guide on using Parameter, Parameters, and ParametersValidator,
see the parameter handling page.
Use ``ParametersValidator``: Prefer
ParametersValidatorover the deprecatedcheckParametersfunctions for new code.Validate Early: Validate parameters at the beginning of functions or constructors.
Type Safety: Use template arguments to restrict parameter types when possible.
Required Parameters: Mark required parameters explicitly using the
requiredoption.Incompatible Parameters: Use
addIncompatibleParametersListto enforce mutual exclusivity between parameters.Accessing Parameters:
Non-throwing mode (recommended):
auto ctx = Context{}; const auto value = get<int>(ctx, params, "ParameterName"); if (isInvalid(value)) { return ctx.registerErrorMessage("invalid parameter"); }
Throwing mode (for constructors or functions with
attributes::Throwing):const auto value = get<int>(throwing, params, "ParameterName");
Checking Parameter Existence: Use the
containsfunction:if (contains(params, "ParameterName")) { // Parameter exists }
Memory Management
Smart Pointers: Prefer
std::unique_ptrandstd::shared_ptrover raw pointers. Usemake_uniqueandmake_sharedhelper functions.Ownership: Be explicit about ownership. Use raw pointers or references for non-owning relationships.
Move Semantics: Use move semantics for efficient transfers of resources. Mark move constructors and move assignment operators as
noexcept.Copy Semantics: If copying is expensive or not meaningful, delete the copy constructor and copy assignment operator.
Class Design
Use struct Over class: Prefer
structfor types where public members are declared first. This aligns with the project’s convention of declaring public members before private or protected ones.Virtual Destructors: All base classes must have a virtual destructor. Mark it as
noexceptand= defaultif possible.virtual ~MyBaseClass() noexcept = default;
Override Specifier: Always use the
overridespecifier for virtual functions that override a base class method.Final Specifier: Use the
finalspecifier for classes or methods that should not be further derived or overridden.Default Member Functions: Use
= defaultfor default constructors, destructors, copy constructors, and assignment operators when appropriate.Deleted Member Functions: Use
= deleteto explicitly delete member functions that should not be used.Rule of Five: If you define any of the copy constructor, copy assignment operator, move constructor, move assignment operator, or destructor, you should define all of them.
Testing
Test Framework: Use the TFEL test framework for unit tests (see TFELTests documentation).
Test Naming: Test files should be named
<ClassOrFeature>Test.cxx.Test Structure: Each test case should be a class inheriting from
tfel::tests::TestCase.Test Assertions: Use the
TFEL_TESTS_CHECK,TFEL_TESTS_CHECK_EQUAL,TFEL_TESTS_ASSERT, and similar macros for assertions.Error Handling in Tests: Tests should verify both success and failure cases, including error messages.
Documentation
Doxygen: All public APIs must be documented using Doxygen comments.
Examples: Provide usage examples in the documentation where helpful.
Cross-References: Use
\see,\ref, and\sato link to related documentation.Code Comments: Use inline comments sparingly. Prefer self-documenting code. When necessary, use
//for short comments and/*! ... */for Doxygen comments.
Performance
Avoid Copies: Use references, pointers, or move semantics to avoid unnecessary copies.
Reserve Capacity: Reserve capacity for containers (e.g.,
std::vector) when the size is known in advance.Algorithms: Prefer standard library algorithms (e.g.,
std::sort,std::find) over hand-written loops when appropriate.Profiling: Use the
MGIS/Profiling.hxxutilities for profiling critical sections.
Modern C++ Features
Use Modern Features: Leverage C++20 features such as:
Concepts
Ranges
std::spanDesignated initializers
[[nodiscard]]attributestd::optionalstd::variant
Avoid Legacy Features: Avoid C-style casts, raw arrays, and manual memory management.
Type Safety: Use
enum classinstead of plainenumfor type safety.Constants: Use
constexprfor compile-time constants.
Miscellaneous
Boolean Naming: Use
is,has, orcanprefixes for boolean functions:bool isValid(...) noexcept; bool hasFeature(...) const noexcept; bool canDoSomething(...) const noexcept;
Getter/Setter Naming: Use the noun form for getters and
setprefix for setters:auto getValue() const noexcept; void setValue(...) noexcept;
Avoid Abbreviations: Use descriptive names. Avoid abbreviations unless they are widely understood (e.g.,
ctxfor context,paramsfor parameters).Consistency: Follow the existing naming and coding conventions in the codebase.
Line Length: Keep lines under 80 characters where possible.
Trailing Commas: Use trailing commas in lists, parameter lists, and similar constructs for easier diffs and version control.
Initialization: Prefer uniform initialization (braces
{}) over parentheses().