1
0
forked from Alepha/Alepha

Move some docs to a docs dir.

This lets me have a dir named `Format`, which I want.
This commit is contained in:
2024-04-04 18:12:54 -04:00
parent 53a4d91a23
commit 6d60639b1c
3 changed files with 0 additions and 0 deletions

388
docs/Format Normal file
View File

@ -0,0 +1,388 @@
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: None.
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.
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. A 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;
};
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 <TYPE>
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.

18
docs/README.doxygen Normal file
View File

@ -0,0 +1,18 @@
To generate a doxygen reference for Alepha, run "doxygen doxyfile.alepha"
A subfolder, DoxygenAlepha, will be created
Point a browser to the html/index.html within DoxyAlepha to bring up the main page.
The .gitignore file here excludes the DoxyAlepha subfolder in its entirety.
If there are too many namespaces in the Class List, consider using Class Index instead.
Note:
Upgraded to version 1.10.0 to, hopefully get better c++20, including concepts
Todo:
- create a script to execute doxygen
- as part of that script preprocess source file (c++ and header) to remove or simplify namespaces
- set the project brief and project logo in doxygen config
- customize header and footer

223
docs/Style Normal file
View File

@ -0,0 +1,223 @@
Alepha library C++ style Guide
This guide shall follow the format of "Rule", then "Examples", then "Reasoning", then "Exceptions".
Some of these clauses are omitted. No portion of the C++ language itself is off-limits, unless explicit mention
is made banning that feature -- which is rare. The Alepha C++ philosophy sees C++ as a whole langauge and
does not seek to provide a "safe subset". The few banned features tend to be those which are either deprecated
or generally unused.
Note that this "style guide" covers the style of the code in the sense of how to write the code itself,
not the rendering/format. For that guide, please see the "Format" file.
General Coding and Language Rules:
0. The Alepha C++ library shall only contain code written for ISO/ANSI C++23, compilation with any other language
standard is incidental. Modifying a component of Alepha to compile with a non-compliant compiler (while still
remaining compliant to the standard) is sometimes necessary. Deprecated features should be avoided, wherever
possible. C++98 code (with precious few exceptions) will work unchanged in C++23.
Reason: Alepha is a modern C++ library technology.
Exceptions: None. ("#pragma once" is used, as if it were part of the standard. This is widely supported,
and when C++20's modules become more prevalent, the library will move to those.)
Note: Even among all "compliant" compilers, some quirks prevent perfect, and ideal C++23 code.
Even in such cases, the code which does compile is still expected to be C++23 compliant.
1. All names and user-defined tokens shall avoid C89, C99, C11, C++98, C++03, C++11, C++14, C++17, C++20, and C++23
implementation reserved names, in all contexts.
- Names shall not begin with an underscore.
- Names shall not contain two consecutive underscores.
- User defined names include: functions, macros, classes, constants, and header guards.
- Be mindful of C++'s little known reserved words: compl or not and xor bit_and bit_or bit_xor
Reason: Alepha is a portable C++ library, and thus must be able to compile with any C++23 compiler. The presence
of implementation reserved names in code can cause potential issues. Even in a context where the name wouldn't
be reserved, consider it to be reserved.
Exceptions: NONE
2. `#define`s of a keyword to anything else are prohibited.
Reason: #defining a keyword yields non-portable behavior. On a fully conforming C++23 compiler, all features
are present, and useful. Not all compilers behave in the same way, when it comes to violating the standard.
Exceptions: None. For a while, it was necessary to supplement some compilers with `#define override` or
similar hacks, for not-yet-supported C++11 features. By this point in time, the new features in C++20 and
C++23 cannot be so easily "hacked in".
3. No C++23 core-language features shall be proscribed from use, with the exception of avoiding the following
idioms or features (some are banned outright):
- digraphs are forbidden (Trigraphs were removed in 17)
- prefer `new` over `malloc` (except in implementing `::operator new`)
- prefer to use `()` instead of `( void )` in empty parameter functions
- avoid `reinterpret_cast` (except in specific hardware-oriented contexts, or where otherwise necessary)
- rarely use "old C style" casts. (E.g.: `(char *) variable`) -- this is widely known to be dangerous
- never throw from a destructor -- this is not expected by any C++23 libraries
- Avoid the use of general `mutable` members. No good can come from changing values behind const, even locks.
TOCTIOU bugs abound with observers of threadsafe mechanisms. Properly written Atomics primitives still need
to be members of classes as mutable.
Reason: C++23 is a cohesive, well-reasoned language with many tightly-integrated features. Circumscription of
a feature is ill-advised. The above prohibitions are mostly encouraged by choices of the standards committee
over the past 20 or more years. Reinterpret cast, in particular, represents a standards-sanctioned form of
platform specific behavior. Digraphs and trigraphs were a compatibility feature required by the standard, but
are bizarre and unintuitive -- many compilers disabled them unless in strict compatibility modes, and they are
removed in C++23.
Exceptions: Malloc can be used in circumstances where that allocator is necessary. "( void )" parameter lists are
usable in specific 'meant to be included in C' headers -- these must be in their own directory. C style casts
are dangerous, in general, but '(float) 4 / 2' is more readable than: 'static_cast< float >( 4 ) /
static_cast< float >( 2 )' or 'std::divide< float >{}( 4, 2 )' -- prefer function-style casting if possible though.
Never cast with typedefs, or pointers. Destructor throwing is banned, because no C++ STL library can recover
from it, nor can the exception system itself. Reinterpret_cast has platform dependent behavior, and should
only be in platform-dependent code. Digraphs should only exist in obfuscation contests. Assume that
destructors that throw always cause undefined behavior.
4. No non-portable, undefined, implementation defined, or optional behaviors, functions and features shall be
placed into any non-detail portions of the Alepha library. Detail portions shall push implementation-specifics
into isolated files. Only the "Alepha::Windows", "Alepha::Posix", or similar namespaces shall contain any
non-portable constructs.
Reasons: Portability is a top concern for the Alepha library.
Exceptions: Alepha has namespaces dedicated to specific platforms and systems -- those namespaces are suitable
for non-portable code, but no core library portions may rely upon those. (Code must compile with a C++11
compiler and be platform agnostic.) For non-portable namespaces, the code should remain compiler agnostic,
where possible. Always use macro-guards to prevent accidental compilation of files intended for only one compiler.
5. Avoid "pass-by-non-const-reference" for "out-params". Prefer tuples, pairs and aggregate types for multiple
return values, and use exceptions to signal failures.
Reason: "push-to-argument" style was useful in the 1990s when compilers were very bad at optimizing the
class return path. In a post-C++11 world, with both NRV and move semantics, this caveat is no longer pertinent.
Further, this out-parameter style was necessary when exceptions were avoided, as returning an aggregate is
not conducive to error-checking. Alepha fully embraces the C++ exceptions mindset. Exceptions exist in the
language to signal out-of-band errors -- return codes are to be avodied.
Exceptions: Sometimes arguments have to be passed to be modified, like 'operator <<' on std::ostream. The
'operator >>' std::istream operators are also examples of this, but technically fall under the "out-params" rule.
Historically these iostream constructs exist, and we shall still support the iostreams idiom -- it's part of
the standard.
6. Manage all resources with objects and RAII. Prefer to manage only a single resource per RAII class. Do not
ever allow unshepherded or unowned resources to exist. Directly callling "new" is strongly discouraged.
Reason: It's not just a good idea -- all of C++23 is built around the RAII idiom. It's necessary for exception
safety, which is a core part of the Alepha philosophy. An RAII class which has to manage several resources
often represents a design bug. Some classes (like ref-counting-pointer constructs) may need to allocate some
resources to manage another resource -- this is unavoidable, but the other resources are management metadata.
Exceptions: None, whatsoever.
7. Unless a class is intended to directly manage a resource, it ought to have a blank destructor, in the
ideal situation. Prefer `AutoRAII` as the basis of a class designed to use resource management, in the general
case.
Reason: RAII is most effective when each class that actually manages a resource only manages a single resource,
and concept abstraction classes don't have to have any explicit resource management.
Exceptions: Sometimes a destructor of a class may need to call one of its own functions, like a "cancel"
function. A network request, or a thread, for example. This is not entirely an exception, since that class
models that concept as a kind of resource. Conversely, those "cancel" functions are merely an exposition of
the dtor functionality for early voluntary cleanup.
8. Naked `delete` and other resource release calls outside of destructors are banned. Naked `new` and other
resource allocation calls that are assigned to raw unmanaged C types, outside of constructors or constructor
parameters are banned. This rule works hand-in-hand with the previous three rules. Prefer scoped_ptr,
unique_ptr, shared_ptr, weak_ptr, AutoRAII, vector, list, set, map, and other types over rolling your own
resource management types, where possible. Calls to 'new' and 'delete' are bad code smells. Use of raw
primitive pointers is also such a code smell. Consider using `single_ptr` or a native reference when working
with items held at a distance. Alepha::single_ptr is only constructible from an owned resource, or another
single_ptr.
Reason: C++ RAII is the only safe way to manage resources. By limiting the resource release code to only
exist in dtors, it limits the scope of code needed to audit for leak-safety. Even RAII classes should only
handle resource release through the dtor path. Resource release outside of this path should be viewed as a
glaring bug. Resource acquisition outside of a ctor, or an argument to a ctor should likewise be seen as a bug.
Raw C types are unsafe for resource management in the face of changing resources.
Exceptions: None. Although passing a lambda calling a release function to an AutoRAII object is technically
an exception, it should be thought of as writing an inline dtor. Note that std::make_unique and
std::make_shared are suitable replacements for new in almost all situations.
9. Avoid the use of #ifdef/else/endif for conditional compilation of code -- prefer to include headers with
well defined code for these purposes.
Reason: Conditional compilation makes code multiply architecturally dependent not independent.
Exceptions: Some small blocks of code (particularly preprocessor control macros) can be if-else-endif for
small specific variances.
Basic Environment Rules:
0. Alepha shall compile on any C++23 compliant compilers. At present gcc-13 is a supported minimum,
and is also the "reference compiler".
Exception: Compiler specific code in detail sections need only work with that compiler. Platform specific code,
in Alepha::<Platform> namespaces need only work on that target platform, but should be portable across compilers
for that platform. Alepha::Posix sections should try to also work in Cygwin, where possible.
1. Header files shall be named with a trailing ".h". Headers shall be entirely compatible with C++.
2. Header files which are safe to include from "C" language parsers shall be organized into a specific
"c_interface" subdirectory of the appropriate domain. Such headers are rarely expected.
Basic Naming Rules:
0. Use good judgement in naming things. This cannot be codified as a set of mechanical rules.
1. Follow this general naming style (there's some room for flexibility within):
* ClassesAndTypedefsLikeThis
* PublicNamespaceAlsoLikeThis
* functionsAndVariablesLikeThis
* ALEPHA_PATH_TO_MACROS_GET_NAMED_IN_VERY_LONG_STYLE_AND_OBNOXIOUS_WITH_CAPS_AND_UNDERSCORES
* meta_programming_things_like_this
* TCP_IP_Socket or connectToTCP_IP_Socket-- If the entity name contains abbreviations, separate the abbreviation by `_`,
don't case flatten things like TCP to Tcp.
* Follow STL and related C++ Standards names, where appropriate (`push_back` not `pushBack`).
2. Follow the general module-namespace technique defined in the NAMESPACES document.
3. Name the private module namespace within files as ` ::detail:: FileName_m`, such that `FileName.h`
provides the namespace. This provides a simple transform:
Alepha/IOStream/String.h -> Alepha::IOStream::String_m
4. General file naming is case-sensitive. Every major modern operating system supports a case-sensitive
filesystem. Use it. Windows' default filesystem is case-preserving. This case-preserving property
should suffice for most file naming situations; however, if `foo.h` and `Foo.h` both exist, it might
cause a problem. That problem is more easily remedied by using a case-sensitive filesystem than by
putting an onus for name mangling onto a project.
5. Name files after the primary component in them, if the file makes a single component available.
Example: `class Alepha::Foobar::Quux` should be defined in `Alepha/Foobar/Quux.h`, if defined in a single
file. The full public name of that class would be `Alepha::Foobar::exports::Quux_m::Quux`. The private
name can be anything, of course, but would typically be `Alepha::Foobar::detail::Quux_m::exports::Quux`.
6. Name files which provide families of facilities without leading capitals. Those names shouldn't be
confused for classes.
7. Name functions with a verb, where appropriate. Don't name observers with a `get` verb.
8. Avoid names with `do`, `run`, `compute`, or `execute` in them for functions. Remember functions
`do`, they aren't things.
9. Avoid names with `-er`, `Manager`, `Owner`, or `Holder` in them for classes. Remember that
classes don't `do`, they're not functions.