diff --git a/Style b/Style new file mode 100644 index 0000000..30dd261 --- /dev/null +++ b/Style @@ -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:: 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.