Basic Code Formatting Rules: 0) Like most things in Alepha, these rules are "strong guidelines". When in doubt, these rules form a baseline of good current practice. However, there's no substitute for good judgement. Therefore, a large degree of formatting latitude is afforded to code. The primary value being sought is clarity, not consistency. Therefore, new stylistic forms are constantly being adopted for specific cases -- it is cumbersome (at best) to keep track of all of them here. Reason: Format should convey meaning. It's fine to invent new conventions. Documenting them is good too, but it shouldn't be cumbersome to do so -- thus this file itself will always be an incomplete representation of the format. Exceptions: Certain core guidelines are universally required, like tabs. 1) Alepha code uses tab character ('\t', ASCII:0x09) for indentation. One tab per indent level. Tabs are not to be used for alignment, except with other tabs. Tabs have no expected set width. Reason: Tabs permit customization of code indent level to a reader's taste via the tabwidth settings of editors. Exceptions: None. The tab character as _indent only_ permits cooperation amongst those with different visual preferences. 2) Semicolons on all lines shall appear immediately after the last syntactic character, without a separating space, immediately followed by a newline (or trailing comment). It is permissible put a semicolon on its own line to facilitate a block structure where appropriate for the language. Examples: ++i; f( a, b ); const bool value= true or a or b ; 3) Declare only one variable per "line". Examples: int x; float *y; int &xr= x; Reason: C++ declaration grammar inherits ambiguities and oddities from C. By declaring one variable per line, we sidestep this issue. Exceptions: In some cases it may be clearer to declare multiple variables of a non pointer and non reference type on a single line: Examples: int x, y, z; std::string s1, s2; 4) All bracing constructs, "(), {}, [], and <>", must have a space between the opening brace and the first argument, and between the last argument and the closing brace. If there are no arguments then there should be no spaces between the opening and closing brace. No space shall exist between a keyword or an entity name and the following opening bracing construct. A newline character counts as a space for these constructs, thus permitting block-like structure. Examples: if( someFunction( argument ) ) return 1; // Good. Notice that there is no trailing space after the "if" std::vector< std::map< std::string, std::set< std::string > > > complexDataEntity; // Even for templates complexDataEntity[ 10 ][ "Hello "].find( "Something" ); // Even for array-like operations. std::cout << [](){ return "Hello World!"; }() << std::endl; // For one-lining lambdas and initializers, the rule applies. for( int i= 0; i < 10; ++i ) std::cout << i << std::endl; // Even within a loop construct. Reason: Extra space can be visually helpful. Many styles encourage visual cues to code flow. Exceptions: Spacing should not be used in #include statements. Examples: #include 5) All 2-argument operators that do not modify the left hand side (binary-function operators) must have a space on both sides of the operator, whereas data-modifying equals-based operators like ' =, +=, -=, *=, /=, ^=, |=, &=, ~=, <<=, >>=, %= ' shall have no space to their left, thus binding them to the expression that they modify. Note that ' !=, <=, >= ' are not assignment-like operators, but a comparison operators. Comma "operators" shall also follow left-hand no spacing rules, as this matches their natural use in other circumstances. Examples: int a= 10; int b= 11, c= 12; c= ( a * b + c ) / ( a * 22 ); a%= c / b; if( ( a != b ) or ( c < a ) && ( a= b ) ) return true; Reason: Extra visual space helps. Cuddling the assignment operators helps distinguish them from comparison operators. "<=" vs "<<=" and "==" vs "=". By having extra code-intent presented in a side-channel (spacing), it helps prevent accidental typos and bugs. Exceptions: `<=` when used as a "left fat arrow" operator may be used with the spacing rules that most make sense for their context. `<=[]` on lambdas, for example. `/` when used as a directory separator between path components from `std::filesystem` should also omit surrounding spaces. 6) All unary operators shall "cuddle" their applied variable. Pointers and references shall be be declared with the "&" or "*" attached to the variable name. Examples: int a= 10; ++a; a--; a= ~a; if( !a ) return false; Reason: Somewhat improves readability. For declarations, although "int &" is the type of "int &x;", this does not extend to: "int &x, y". Those are distinct types, but by banning multiple declarations per line, we mostly avoid this issue. 7) Brace placement shall be "Allman/BSD"-like. Opening braces shall be on a new line, closing braces on a new line. Braces shall be indented by the same level as the line of their "control" statement. A braced-block that is not subordinate to a control statement shall be indented as if it had an "if( true )" control statement above it. In addition, for such a non subordinate block, it is strongly recocmmended to have an introducing comment that helps set the block apart. do-while loop shall have the while on its own line, after the closing brace, where possible. Note that one-line compaction is permitted, as is omitting braces for fully subsumed blocks. Examples: int main() { int x= 10; int y= 11; if( x < y ) { std::vector< int > v; if( y % 2 ) { rv.push_back( v ); } // Note that this if block has no parent, but it is indented like it has one. { std::list< int > l( v.begin(), v.end() ); if( x == y ) for( const auto &x: stuff ) { std::cout << x << std::endl; } std::cout << l.size() << std::endl;; } do { std::cout << v.size() << std::endl; } while( false ); } return 0; } Exceptions: Very small functions or blocks can be subsumed into one line, for brevity. 8) Return types shall be placed above the name of the function in definitions, but on the same line for declarations. inline or explicit for ctors, dtors, and conversion functions shall follow this rule as well. Example: bool is_even( int x ); bool is_even( int x ) { return ~x & 1; } 9) Template clauses for definitions shall be on their own line. For declarations, a separate line is optional. Examples: template< typename T > bool is_even( T x ); template< typename T > bool is_even( T x ) { return ( x % 2 ) == 0; } 10) In class definitions, public, protected, and private regions shall be indented beyond the access control keywords, which must also be indented. In switch/case blocks, cases shall be similarly single-indented, as too breaks shall remain single indented. All switch statements shall have a break, return, or throw at the end of each case clause or shall use [[fallthrough]] to document fallthrough behavior, where it is used. Default clauses are not mandatory, but are recommended. (Default clauses might be blank, or all cases might be covered, especially when the argument is an enum.) Examples: class Foobar { private: int x; public: void setX( int newx ) { x= newx; } int getX() const { return x } void operation( int val ) { switch( val ) { case 2: x= 11; break; case 11: x= 2; break; case 12: specialCase(); [[fallthrough]]; default: x= val; break; } } }; struct Open { int x; }; Exception: Indenting break one more than case is acceptable, both as a preference and because an auto formatter may not distinguish between a switch/case/break and a for/break or while/break. 11) Continued lines shall be indented twice past the main parent's indent. When breaking a line on an operator, the next line should begin with an operator. Spaces may also be used to line up nicely for vertical alignment of operators; however, note that spacing alignment starts from the tab-depth of the previous line. (Never use spaces to align to a specific tab depth.) Control structures which have a single subordinate statement on a separate line shall enclose that line in braces. If wrapping becomes necessary with "one line" control structures, then bracing is required. Example: int main() { std::cout << "A really long message which " << "needed to be indented " << "across multiple lines." << std::endl; std::cout << "A really long message which " << "needed to be indented " << "across multiple lines. " << "This message was aligned " << "on ostream operators." << std::endl; if( condition ) action(); if( moreComplicatedCondition ) { moreComplicatedAction(); } } Reasons: It is important to know when lines have wrapped. A specific visually distinct style helps make this stand out from other indented scenarios. By avoiding braceless subordinates to control structures we avoid a class of silly maintenance bugs. 12) Initializer lists shall be treated as-if they're part of the ctor body. They get single indented, unless the function is really short. Example: struct StringWrapper { std::string string; explicit StringWrapper( const std::string &s ) : string( s ) {} explicit StringWrapper() : string( "no value" ) {} // A short ctor or other function can be on one-line. }; Exceptions: Single line functions and ctors, and the like are sometimes good to conserve space. Trivial implementations should take trivial amounts of space. 13) Private data shall be declared before the functionality of the class is declared or defined. Constructors, copy assignment, and destructors shall be declared (or defined) before other member functions. Example: template< typename T > class smart_ptr : boost::noncopyable { private: T *ptr; public: explicit inline smart_ptr( T *const p ) : ptr( p ) {} inline ~smart_ptr() { delete p; } T & operator *() const { return *ptr; } T * operator->() const { return ptr; } }; Reasons: It's easier to reason about the correctness of a class's primary invariant mechanism (construction and destruction) when its near the variables and types it manages. Knowing the contents of a class helps to understand about the invariants which must be maintained. Some argue this makes finding the public interfaces harder -- but Hera classes are designed to be perused by doxygen, and thus this documentation shall serve as a primary public usage reference. Within Hera, the only reason to read a header file is to understand more about a particular implementation. Exceptions: None. 14) Class parents (inheritance) shall logically be on the same line as the declaration. If on another line, must count as a continuation. Reasons: It helps to isolate a class's name from the parent list, which is (when you think about it) part of the class, and needs indentation. 15) Preprocessor nested scopes (which sometimes do happen) shall indent, by requisite spacing, all characters AFTER the leading "#". Examples: #ifndef SOME_THING # ifndef SOME_OTHER_THING # define NEITHER_THING_IS_AVAILABLE # else # error Must have SOME_THING if SOME_OTHER_THING is around # endif #endif Reasons: Because C preprocessor macros also follow logical scope, it helps to trace their nesting by indentation. By indenting after the leading "#", we help distinguish C++ code from C preprocessor code. Exceptions: The header guards, being ubiquitous, shall not count as a level of indentation in nested preprocessor construct 16) In declaration of entities, the following order of keywords shall always be followed: virtual explicit extern static constexpr consteval constinit inline mutable const volatile For integers & double: unsigned long long int signed short int long double signed and int are each optional. For characters: unsigned char signed char char Each of those char types are three distinct types -- signed or unsigned are NOT optional. Examples: struct Type { virtual inline ~Type() noexcept {} // Yes, this is actually allowed. explicit inline Type() {} static inline const std::string string() { return "String"; } // Allowed, but violates the style guide: long const unsigned volatile int virtual long inline *resource() const; // The same declaration, following the style guide: virtual inline const volatile unsigned long long int *resource() const; }; Reasons: C++ is very forgiving in the order of its type-and-qualifiers, but standardizing them makes finding specific things simpler. There are many mutually exclusive keywords above, like static and virtual and explicit, for example. Many may not know it but 'long const unsigned volatile int virtual long inline *' is a valid token sequence for declaring a member function. Standardization gives us: 'virtual inline const volatile unsigned long long int *', which makes the declaration type far less confusing.