Comparing Ada and High Integrity C++

I have often suspected that use of a safety critical or high integrity coding standard for C++ would yield a level of safety and software reliability approximately equivalent to using Ada with no restrictions.
I have documented a comparison of the High Integrity C++ Coding Standard (HIC) produced by PRQA with standard Ada language features. I was mostly correct in my suspicions. There are some rules in the HIC which apply equally well to Ada, such as a prohibition against the use of the goto statement.
In many instances the HIC rules require a non-trivial amount of code development and verification, while the Ada solution is trivial. For instance, achieving object initialization in C++ requires the use of carefully implemented constructors, while specifying default initialization for Ada records is relatively trivial. Another example is C++ multi-threading. The HIC lists several rules for the use of locks, mutexes, and condition variables. For Ada, the built-in facilities of task Rendezvous for direct task communication, and protected objects for communication through shared buffers, includes implicit control of locks, mutexes, and condition variables.

A PDF version of this document is available at https://drive.google.com/open?id=0B0y7MZFreWQtRy0wR0g3NTF6XzQ

My comparison follows:

_________________________________________________________________________

Comparison of Ada 2012 with the PRQA High Integrity C++ Coding Standard
This document compares the safety critical subset of C++ defined in the High Integrity C++ Coding Standard Version 4.0 (HIC) produced by PRQA and available at www.codingstandard.com.
The comparison will follow the flow of the HIC.

1.     General

1.1.  Implementation Compliance

1.1.1.     Ensure that code complies with the 2011 ISO C++ Language Standard

Compilers often provide features beyond those defined in the Standard, and unrestricted usage of such features will likely hamper code portability. To this end code should be routinely parsed with a separate compiler or code analysis tool apart from the compiler used for production purposes.

Corresponding Ada rule:

Compilers often provide features beyond those defined in the Standard, and unrestricted usage of such features will likely hamper code portability. To this end Ada compilers provide the Ada profile No_Implementation_Extensions, which enforces the restriction to Standard Ada in a standard way. The only thing not caught by this profile is the use of implementation-defined library packages, such as the GNAT packages for the GNAT compiler. Those packages can be caught by adding the No_Dependence profile.

1.2.  Redundancy

1.2.1.     Ensure that all statements are reachable

For the purposes of this rule, missing else and default clauses are considered also.
If a statement cannot be reached for any combination of function inputs (e.g. function arguments, global variables, volatile objects), it can be eliminated.

Corresponding Ada rule:

While Ada contains the same rule, Ada compilers perform reachability analysis and report compilation errors when code is not reachable.
Ada compilers identify missing others clauses in case statements when all possible values are not explicitly included in a case statement.

1.2.2.     Ensure that no expression or sub-expression is redundant

An expression statement with no side effects can be removed or replaced with a null statement without effecting behavior of the program.
Similarly, it is sometimes possible to simplify an expression by removing operands that do not change the resulting value of the expression, for example by multiplying by 1 or 0.
Redundant code causes unnecessary maintenance overhead and may be a symptom of a poor design.

Corresponding Ada rule:

The corresponding Ada rule would be identical. This rule applies to any programming language.

1.3.  Deprecated Features

1.3.1.     Do not use the increment operator (++) on a variable of type bool

Incrementing an object of type bool results in its value being set to true. This feature was deprecated in the 1998 C++ Language Standard and thus may be withdrawn in a later version.
Prefer to use an explicit assignment to true.

Corresponding Ada rule:

The Ada Boolean type is an enumerated type with the values of (False, True). Ada does not allow arithmetic operations on enumerated types. Furthermore, Ada does not provide an increment operator.

1.3.2.     Do not use the register keyword

Most compilers ignore the register keyword, and perform their own register assignments. Moreover, this feature was deprecated in the 2011 Language Standard and thus may be removed in a later version.

Corresponding Ada rule:

Ada does not provide any equivalent to the register keyword in C++.

1.3.3.     Do not use the C Standard Library .h headers

The C standard library headers are included in C++ for compatibility. However, their inclusion was deprecated in the 1998 C++ Language Standard and thus may be withdrawn in a later version.

Corresponding Ada rule:

Ada does not provide a preprocessor and does not interface with C or C++ programs at the source code level.

1.3.4.     Do not use deprecated STL features

The following STL features were deprecated in the 2011 C++ Language Standard and thus may be withdrawn in a later version:
·         Std::auto_ptr
·         Std::bind1st
·         Std::bind2nd
·         Std::ptr_mem_fun_ref
·         Std::unary_function
·         Std::binary_function
Of particular note is std::auto_ptr as it has been suggested that a search and replace of this type to std::unique_ptr may uncover latent bugs to the incorrect use of std::auto_ptr.

Corresponding Ada rule:

Avoid the use of the language features listed in Appendix J of the Ada 2012 Language Reference Manual. Use Ada 2012 aspect specifications instead. Avoiding Annex J features can be enforced through the use of the profile No_Obsolescent_Features.

1.3.5.     Do not use throw exception specifications

Specifying an exception specification using throw( type-id-listopt ) has been deprecated in the 2011 C++ Language Standard and thus may be removed in a future version.
The new syntax is to use noexcept or noexcept( expr).

Corresponding Ada rule:

Ada does not have an equivalent to either noexcept or noexcept(expr). In C++ the noexcept keyword verifies that no exception is thrown in a specified function. The noexcept(expr) declares that a function may throw exceptions for some types but not other types. Both forms of noexcept return a Boolean result.
The SPARK subset of Ada prohibits exceptions and is able to prove that a subprogram will not raise an exception. Exceptions are prohibited because of the difficulty in formally proving the correctness of a program which raises exceptions.

2.     Lexical Conventions

2.1.  Character sets

2.1.1.     Do not use tab characters in source files

Tab width is not consistent across all editors or tools. Code indentation can be especially confusing when tabs and spaces are used interchangeably. This may easily happen where code is maintained in different editors.
In string and character literals \t should be used in preference to a direct tab character.

Corresponding Ada rule:

Use of tabs in Ada source code is subject to the same editor and tool variations as use of a tab in C++ source code.
Insertion of a tab character in a string or character literal or variable should be use Ada.Characters.Latin_1.HT.

2.2.  Trigraph sequences

2.2.1.     Do not use digraphs or trigraphs

Trigraphs are special three character sequences, beginning with two question marks and followed by one other character. They are translated into specific single characters, e.g. \ or ^. Digraphs are special two character sequences that are similarly translated.

Corresponding Ada rule:

Ada does not provide digraph or trigraph sequences.

2.3.  Comments

2.3.1.     Do not use the C comment delimiters /* … */

The scope of C++ comments is clearer; until the end of a logical source line (taking line splicing into account). Errors can result from the use of C comments.

Corresponding Ada rule:

Ada does not allow the use of C comments.

2.3.2.     Do not comment out code

Source code should always be under version control. Therefore keeping old code in comments is unnecessary. It can make browsing,  searching, and refactoring the source code more difficult.

Corresponding Ada rule:

The same rule should apply to any programming language.

2.4.  Identifiers

2.4.1.     Ensure that each identifier is distinct from any other visible identifier

Similarity of identifiers impairs readability, can cause confusion and may lead to mistakes.
Names should not differ only in case (foo/Foo) or in the use of underscores(foobar/foo_bar). Additionally, certain combinations of characters look very similar.
Note: This rule does not require that an identifier cannot be reused.

Corresponding Ada rule:

Ada identifiers are case-insensitive, thus (foo/Foo) are treated as the same identifier.
Ada compilers identify duplicate definition of identifier within the same scope.

2.5.  Literals

2.5.1.     Do not concatenate strings with different encoding prefixes

The C++ Standard permits string literals with the following encoding prefixes: u, U, u8, L. A program that concatenates a pair of string literals with u8 and L prefixes is ill-formed.
The result of the remaining prefix combinations are all implementation defined. For this reason encoding sequences should not be mixed.

Corresponding Ada rule:

Ada provides multiple character encoding sequences. Each sequence is a distinct type and therefore each string type is unique. Ada does not allow mixing data types within a single array.

2.5.2.     Do not use octal constants (other than zero)

Octal constants are specified with a leading digit 0; therefore, literal 0 is technically an octal constant.
Do not use any other octal literals, as based on unfamiliarity, this could be confusing and error prone.

Corresponding Ada rule:

Ada allows the use of any number base from 2 through 16. A literal of a particular number base is expressed as base#digits#  (e.g 2#111# is the binary equivalent of 7 base 10) except for base 10, which is implied by simply expressing the digits. Therefore, 11 and 011 are interpreted as 11 base 10.

2.5.3.     Use nullptr for the null pointer constant

The 2011 C++ Language Standard introduced the nullptr keyword do denote a null pointer constant.
The NULL macro and constant expression 0 can be used in both pointer contexts and integral contexts. nullptr, however, is only valid for use in pointer contexts and so cannot be unexpectedly used as an integral value.

Corresponding Ada rule:

Ada access types are functionally equivalent to pointers but are not pointers. Ada access types can be set to null, which is not a numerical value and cannot be confused as an integral value. The address of a variable can be set using with address aspect of a variable declaration:
Foo : Integer with Address => 16#3ff#;

3.     Basic Concepts

3.1.  Scope

3.1.1.     Do not hide declarations

Reusing the same identifier for different declarations is confusing and difficult to maintain. If the hiding declaration is later removed, or the identifier is renamed, a compilation error may not be generated, as the declaration that was previously hidden will now be found.
While hidden namespace scope identifiers can still be accessed with a fully qualified name, hidden block scope identifiers will not be accessible.
In C++ it is possible for the same identifier to refer to both a type and an object or a function. In this case the object or function will hide the type.

Corresponding Ada rule:

Use of an identifier in a local scope that matches a visible identifier in a dependent package will result in a compiler error. Use of an identifier in an inner block will hide the use of the same identifier an enclosing block.
Ada does not allow an identifier to refer to both a type and an object or a function within the same or enclosing scopes.

3.2.  Program and linkage

3.2.1.     Do not declare functions at block scope

A declaration for a function should not be common to its definition, any redeclarations, and any calls to it.
To ensure that the same type is used in all declarations, functions should always be declared at namespace scope(See Rules 7.4.3: “Ensure that an object or a function used from multiple translation units is declared in a single header file” and 7.4.1: “Ensure that any objects, functions, or types to be used from a single translation unit are defined in an unnamed namespace in the main source file”).

Corresponding Ada rule:

Ada provides the package construct to enforce name space and encapsulation. Any type, object, subprogram, or task used by more than one compilation unit must be declared within a package. Ada provides no other means to share declarations between compilation units.
Ada does allow a single subprogram or task to define types, objects, subprograms or tasks to be used only within the defining subprogram or task.

3.3.  Storage duration

3.3.1.     Do not use variables with static storage duration

Variables with linkage (and hence static storage duration), commonly referred to as global variables, can be accessed and modified from anywhere in the translation unit if they have internal linkage, and anywhere in the program if they have external linkage. This can lead to uncontrollable relationships between functions and modules.
Additionally, certain aspects of the order of initialization of global variables are unspecified and implementation defined in the C++ Language Standard. This can lead to unpredictable results for global variables that are initialized at run-time (dynamic initialization).
This rule does not prohibit the use of a const object with linkage, so long as:
·         It is initialized through static initialization
·         The object is no ODR used
The order of initialization of block scope objects with static storage is well defined. However, the lifetime of such an object ends at program termination, which may be incompatible with future uses of the code, e.g. as a shared library. It is preferable to use objects with dynamic storage duration to represent program state, allocated from the heap or a memory pool.

Corresponding Ada rule:

The only way to share variables across compilation units in Ada is by declaring those variables within a package. The order of initialization of constants is controlled by package elaboration rules. While Ada does provide default elaboration rules, it also provides elaboration pragmas allowing the programmer to specify elaboration order for packages.

3.4.  Object lifetime

3.4.1.     Do not return a reference to a pointer or an automatic variable defined with the function

The lifetime of a variable with automatic storage duration ends on exiting the enclosing block. If a reference or a pointer to such a variable is returned from a function, the lifetime of the variable will have ended before the caller can access it through the returned handle, resulting in undefined behavior.

Corresponding Ada rule:

When an object is destroyed in Ada, whether the object was created through allocation from a storage pool, or on the program stack, the Finalize procedure for that object is called. There are no exceptions in Ada.
Ada accessibility rules, which prevent dangling references, are written in terms of accessibility levels, which reflect the run-time nesting of masters. As explained in 7.6.1, a master is the execution of a certain construct, such as a subprogram_body. An accessibility level is deeper than another if it is more deeply nested at run time. For example, an object declared local to a called subprogram has a deeper accessibility level than an object declared local to the calling subprogram. The accessibility rules for access types require that the accessibility level of an object designated by an access value be no deeper than that of the access type. This ensures that the object will live at least as long as the access type, which in turn ensures that the access value cannot later designate an object that no longer exists. The Unchecked_Access attribute may be used to circumvent the accessibility rules.
A given accessibility level is said to be statically deeper than another if the given level is known at compile time to be deeper than the other for all possible executions. In most cases, accessibility is enforced at compile time by Legality Rules. Run-time accessibility checks are also used, since the Legality Rules do not cover certain cases involving access parameters and generic packages.

3.4.2.     Do not assign the address of a variable to a pointer with a greater lifetime

The C++ Standard defines 4 kinds of storage duration:
·         Static
·         Thread
·         Automatic
·         Dynamic
The lifetime of objects with the first 3 kinds of storage duration is fixed, respectively:
·         Until program termination
·         Until thread termination
·         Upon exiting the enclosing block
Therefore, undefined behavior will likely occur if an address of a variable with automatic storage duration is assigned to a pointer with static storage duration, or one defined in an outer block. Similarly, for a thread_local  variable aliased to a pointer with static storage duration.
If using high_integrity::thread, then references or pointers with local storage duration should not be passed into threads that have the high_integrity::DETACH property.

Corresponding Ada rule:

Ada accessibility rules, which prevent dangling references, are written in terms of accessibility levels, which reflect the run-time nesting of masters. As explained in 7.6.1, a master is the execution of a certain construct, such as a subprogram_body. An accessibility level is deeper than another if it is more deeply nested at run time. For example, an object declared local to a called subprogram has a deeper accessibility level than an object declared local to the calling subprogram. The accessibility rules for access types require that the accessibility level of an object designated by an access value be no deeper than that of the access type. This ensures that the object will live at least as long as the access type, which in turn ensures that the access value cannot later designate an object that no longer exists. The Unchecked_Access attribute may be used to circumvent the accessibility rules.
A given accessibility level is said to be statically deeper than another if the given level is known at compile time to be deeper than the other for all possible executions. In most cases, accessibility is enforced at compile time by Legality Rules. Run-time accessibility checks are also used, since the Legality Rules do not cover certain cases involving access parameters and generic packages.

3.4.3.     Use RAII for all resources

Objects with non-trivial destructors and automatic storage duration have their destructors called implicitly when they go out of scope. The destructor will be called for both normal control flow and when an exception is thrown.
The same principle does not apply for a raw handle to a resource, e.g. a pointer to allocated memory. By using a manager class, the lifetime of the resource can be correctly controlled, specifically by releasing it in the destructor.
This idiom is known as Resource Allocation Is Initialization (RAII) and the C++ Language Standard provides RAII wrappers for many resources, such as:
·         Dynamically allocated memory, e.g. std::unique.ptr
·         Files, e.g. std::ifstream
·         Mutexes, e.g. std::lock_guard

Corresponding Ada rule:

The Ada language provides the package Ada.Finalization, which defines two data types:
·         Controlled  – which provides the procedures:
o   Initialize
o   Adjust
o   Finalize
·         Limited_Controlled  - which provides the following procedures
o   Initialize
o   Finalize

3.5.  Types

3.5.1.     Do not make any assumptions about the internal representation of a value or an object

Avoid C++ constructs and practices that are likely to make your code non-portable:
·         A union provides a way to alter the type ascribed to a value without changing its representation. This reduces type safety and is usually unnecessary. In general it is possible to create a safe abstraction using polymorphic types.
·         Integer types other than signed / unsigned char have implementation defined size. Do not use integer types directly, instead use size specified typedefs, defined in a common header file, which can be easily adjusted to match a particular platform.
·         Do not mix bitwise arithmetic operations on the same variable, as this it likely to be non-portable between big and little endian architectures.
·         Do not assume the layout of objects in memory, e.g. by comparing pointers to different objects with relational operators, using the offsetof macro, or performing pointer arithmetic with unspecified or implementation defined layout.

Corresponding Ada rule:

Ada also has predefined types. In the case of the predefined integer and floating point types it is advised that the programmer define types or subtypes which better meet their needs. Ada allows the programmer to specify the valid range of values for numeric types, along with specific memory characteristics.
Ada allows the programmer to define the size of an object or a subtype in bits. The size may of an integer, for instance, may not exceed the size of the largest integer representation allowed on the target hardware. The size of a component of a compound type may be specified, and the specific memory layout of record components may be specified. Record components can be less than a word in size, or their size may extend across multiple words.
The size of an object can be interrogate at run time. For example, a packed array of Boolean values will represent each array element in one bit. Each bit can be individually indexed through the standard Ada array indexing notation.

4.     Standard Conversions

4.1.  Array to pointer conversion

When an array is bound to a function parameter of pointer type the array is implicitly converted to a pointer to the first element of the array.
In order to retain the array dimension, the parameter should be changed to a reference type or changed to a user defined type such as std::array.

Corresponding Ada rule:

Ada does not provide array to pointer conversion. An array is a first class type. The dimension and index values of the array are available wherever the array object is visible.

4.2.  Integral conversions

4.2.1.     Ensure that the U suffix is applied to a literal in a context requiring an unsigned integral expression

If a literal is used to initialize a variable of unsigned type, or with an operand of unsigned type in a binary operation, the U suffix should be appended to the literal to circumvent an implicit conversion, and make the intent explicit.

Corresponding Ada rule:

Ada does not provide implicit conversions between data types.  An unsigned type in Ada is known as a modular type. All integer literals in Ada are of the type Universal Integer. A value of type Universal Integer will be implicitly converted to the target type during assignment or in an arithmetic operation. Ada will raise a compile-time error if the literal is not within the range of the target type during an assignment. Ada will raise a run time exception if the result of the arithmetic operation is not within the range of the target type.
The programmer is allowed to define unique numeric data types. There are no implicit conversions between programmer defined data types, even when they two types share the same memory layout. Ada is strongly typed.

4.2.2.     Ensure that data loss does not demonstrably occur in integral expressions

Data loss can occur in a number of contexts:
·         Implicit conversions
·         Type casts
·         Shift operations
·         Overflow in signed arithmetic operations
·         Wraparound in unsigned arithmetic operations
If possible, integral type conversions should be avoided altogether, by performing operations in a uniform type matched to the execution environment.
Where data storage is a concern, type conversions should be localized with appropriate guards (e.g. assertions) to detect data loss.
Similar techniques can be used to guard shift and arithmetic operations, especially where the data is tainted in a security sense, i.e.  a malicious user can trigger data loss with appropriately crafted input data.
Data loss may also occur if high order bits are lost in a left shift operation, or the right hand operator of a shift operator is so large that the resulting value is always 0 or undefined regardless of the value of the left hand operand.
Therefore, appropriate safeguards should be coded explicitly (or instrumented by a tool) to ensure that data loss does not occur in shift operations.
For the purposes of this rule integral to bool conversions are considered to result in data loss as well. It is preferable to use equality or relational operators to replace such type conversions. The C++ Language Standard states that unless the condition of an if, for, while, or do statement had a Boolean type, it will implicitly converted to bool.
Note: An implicit conversion using an operator bool declared as explicit does not violate this rule.

Corresponding Ada rule:

All integral data operations in Ada are protected by range checks. Any violation of a range specification at run time will result in the exception Constraint_Error.
Ada only allows bit shifting operations on unsigned types.
The Ada Boolean type is not numeric, and therefore does not undergo conversion to an integral type. All conditions of an if, for, or while statement must result in a Boolean value.

4.3.  Floating point conversions

The C++ Standard provides 3 floating point types: float, double, long double that, at least conceptually, increase in precision.
Expressions that implicitly or explicitly cause a conversion from long double to float or double, and from double to float should be avoided as they may result in data loss.
When using a literal in a context that requires a type float, use the F suffix, and for consistency use the L suffix in a long double context.

Corresponding Ada rule:

Ada provides a predefined floating point type named float, which should provide at least 6 decimal digits of precision. The programmer is allowed to define custom floating point types with a user specified range and precision.
There is no implicit conversion between floating point types.
Ada also provides the ability to define fixed point types. Fixed point types are implemented as scaled integers. Fixed point types have a constant number of digits after the decimal place, while floating point types have a variable number of digits after the decimal place (or none at all) depending upon the magnitude of the value represented.
There is no implicit conversion between numeric types in Ada.

4.4.  Floating-integral conversions

4.4.1.     Do not convert floating values to integral values except through use of standard functions

An implicit or explicit conversion from a floating to an integral type can result in data loss due to the significant difference in the respective range of values for each type.
Additionally, floating point to integral type conversions are biased as the fractional part is simply truncated instead of being rounded to the nearest integral value. For this reason use one of the standard library functions: std::floor and std::ceil is recommended if a conversion to an integral type is necessary.
Note: A return value of std::floor and std::ceiling is of floating type, and an implicit or explicit conversion of this value to an integral type is permitted.

Corresponding Ada rule:

There are no implicit conversions between Ada numeric types. Every floating point type and subtype has the attributes S’Ceiling and S’Floor defined to calculate the Ceiling and Floor of the floating point value.
If an explicit conversion between a floating point value and an integral value is performed the conversion  will include a range check. If the resulting value is out of range of the integral type the exception Constraint_Error will be raised.

5.     Expressions

5.1. Primary expressions

5.1.1.     Use of symbolic names instead of literal values in code

Use of “magic” numbers and strings in expressions should be avoided in preference to constants with meaningful names.
The use of named constants improves both the readability and maintainability of the code.

Corresponding Ada rule:

Use of constants is recommended over the use of literals.

5.1.2.     Do not rely on the sequence of evaluation within an expression

To enable optimizations and parallelization, the C++ Standard uses a notation of sequenced before, e.g.:
·         Evaluation of a full expression is sequenced before the next full-expression to be evaluated
·         Evaluation operands of an operator are sequenced before evaluation of the operator
·         Evaluation of arguments in a function call are sequenced before the execution of the function
·         For built-in operators &&, ||,  and operator ? evaluation of the first operand is sequenced before evaluation of the other operand(s)
This defines a partial order on evaluations, and where two evaluations are unsequenced with respect to one another, their execution can overlap. Additionally, two evaluations may be indeterminately sequenced, which is similar, except that the execution cannot overlap.
This definition leaves great latitude to a compiler to re-order evaluation of sub-expressions, which can lead to unexpected, and even undefined behavior. For this reason, and to improve readability an expression should not:
·         Have more than one side effect
·         Result in the modification and access of the same scalar object
·         Include a sub-expression that is an assignment operation
·         Include a sub-expression that is a pre- or post-increment/decrement operation
·         Include a built-in comma operator (for overloaded comma operation see Rule 13.2.1: “Do not overload operators with special semantics”)

Corresponding Ada rule:

In Ada assignment is not an expression, and cannot therefore be part of a compound expression. Furthermore, there are no pre- or post-increment/decrement expressions in Ada. These limitations limit the problems associated with violation of Rule 5.1.2.
Ada 2012 does allow the use of “in out” parameters in functions, which can lead to order dependencies. Early versions of Ada prohibited “in out” parameters in functions, allowing only “in” parameters, to avoid this problem. It is still recommended that functions only use “in” parameters except when interfacing with other languages, such as C and C++.

5.1.3.     Use parentheses in expressions to specify the intent of the expression

The effects of precedence and associativity of operators in a complex expression can be far from obvious. To enhance readability and maintainability of code, no reliance on precedence or associativity should be made, by using explicit parentheses, except for:
·         Operands of assignment
·         Any combination of + and operations only
·         Sequence of && operations only
·         Sequence of ||operations only

Corresponding Ada rule:

In every language it is recommended to use explicit parentheses instead of relying upon precedence and associativity rules.
In Ada the logical and operator is and. The logical or operator is or. The equivalent of the C++ && is “and then” and the equivalent of the C++ || is “or else”.

5.1.4.     Do not capture variables implicitly in a lambda

Capturing variables helps document the intention of the author. It also allows for different variables to be captured by copy and captured by reference within the same lambda.
Exception:
It is not necessary to capture objects with static storage duration or constants that are not ODR used.
However, the use of objects with static storage duration should be avoided. See Rule 3.3.1: ”Do not use variables with static storage duration”.

Corresponding Ada rule:

Ada does not provide lamdas. It is possible to pass a subroutine (function or procedure) as a generic parameter.

5.1.5.     Include a (possibly empty) parameter list in every lambda

The lambda-declarator is optional in a lambda expression and results in a closure that can be called without any parameters.
To avoid any visual ambiguity with other C++ constructs, it is recommended to explicitly include ( ), even though it is not strictly required.

Corresponding Ada rule:

Ada does not provide lamdas. It is possible to pass a subroutine (function or procedure) as a generic parameter.

5.1.6.     Do not code side effects into the right-hand operands of: &&, ||, sizeof, typeid or a function passed to condition variable::wait

For some expressions, the side effect of a sub-expression may not be evaluated at all or can be conditionally evaluated, that is, evaluated only when certain conditions are met. For Example:
·         The right-hand operands of the && and || operators are only evaluated if the left hand operand is true and false respectively.
·         The operand of sizeof is never evaluated.
·         The operand of typeid is evaluated only if it is a function call that returns reference to a polymorphic type.
Having visible side effects that do not take place, or only take place under special circumstances makes the code harder to maintain and can also make it harder to achieve a high level of test coverage.
Conditional Variable: wait member
Every time a waiting thread wakes up, it checks the condition. The wake-up may not necessarily happen in direct response to a notification from another thread. This is called a spurious wake. It is indeterminate how many times and when such spurious wakes happen. Therefore it is advisable to avoid using a function with side effects to perform the condition check.

Corresponding Ada rule:

The same rule applies to the Ada “and then” and “or else” logical operations. The right hand side of the “and then” operation is only evaluated if the left hand side is True. The right hand side of the “or else” operation is only evaluated if the left hand side is False.

5.2.  Postfix expressions

5.2.1.     Ensure that pointer or array access is demonstrably within bounds of a valid object

Unlike standard library containers, arrays do not benefit from bounds checking.
Array access can take one of the equivalent forms: *(p + i) or p[i], and will result in undefined behavior, unless p and p + i point to elements of the same array object. Calculating (but not dereferencing) an address one past the last element of the array is well defined also. Note that a scalar object can be considered as equivalent to an array dimensioned to 1.
To avoid undefined behavior, appropriate safeguards should be coded explicitly (or instrumented by a tool), to ensure that array access is within bounds, and that indirection operations (*) will not result in a null pointer dereference.

Corresponding Ada rule:

Ada makes these checks automatically, so there is no need for manually making them or using some separate tool that also needs to be validated.  The compiler will statically check values for array bounds violations and issue a compilation error when a bounds violation is detected. The Ada compiler also automatically inserts bounds checking into array accesses as needed.
Moreover, the range of the index values for an array can be any contiguous range of discrete values. Ada array indexing is not tied to pointer arithmetic and the lowest bound for an array need not be 0. Ada array definitions include a definition of the array type and range as well as the element type. Arrays have well defined attributes available to the programmer wherever the array object is visible.
Table 1 Attributes of array A
Array Attribute
Description
A’Length
The number of elements in the array
A’First
The value of the lowest index value for the array
A’Last
The value of the highest index value for the array
A’Range
The range of index values for the array; equivalent to A’First..A’Last
Iterating through an array safely in Ada is done using the Ada for loop:
For loop syntax
Description
for I in A’Range loop
   … do something
end loop;
Iterate through the index values for the range of array A, doing something for each index value.
for value of A loop
  … do something
end loop;
Iterate through array A referencing each value of A in turn. This is equivalent to a “for each” loop in some other languages

5.2.2.     Ensure that functions do not call themselves, either directly or indirectly

As the program stack tends to be one of the more limited resources, excessive use of recursion may limit the scalability and portability of the program. Tail recursion can readily be replaced with a loop. Other forms of recursion can be replaced with an iterative algorithm and worklists.

Corresponding Ada rule:

If you want to enforce this rule in Ada you can use the profile No_Recursion.

5.3.  Unary expressions

5.3.1.     Do not apply unary minus to operands of unsigned type

The result of applying a unary minus operator (-) to an operand of unsigned type (after integral promotion) is a value that is unsigned and typically very large.
Prefer to use the bitwise complement (~) operator instead.

Corresponding Ada rule:

The rule here is the same for Ada. The result for an Ada unsigned type is well defined, but may not be what the programmer intends.

5.3.2.     Allocate memory using new and release it using delete

C style allocation is not type safe, and does not invoke constructors or destructors. For this reason only operators new and delete should be used to manage objects with dynamic storage duration.
Note: Invoking delete on a pointer allocated with malloc or invoking free on a pointer allocated with new will result in undefined behavior.

Corresponding Ada rule:

All Ada allocation / deallocation mechanisms are type safe.

5.3.3.     Ensure that the form of delete matches the form of new used to allocate the memory

The C++ Standard requires that the operand to the delete operator is either:
·         a null pointer
·         pointer to a non array object allocated with new
·          pointer to a base class3 subobject of a non array object allocated with new
Similarly, the operand to the delete[] operator is either:
·         a null pointer
·         pointer to an array object allocated with new[]
In order to avoid undefined behavior, plain and array forms of delete and new should not be mixed.

Corresponding Ada rule:

Ada does not have this problem.
There are no special forms of new for allocating arrays in Ada.  An Ada array is a first class type and the compiler knows the size of an array. The new operator allocates the memory needed to hold an object of the type specified.
Ada does requires the generic function Ada.Unchecked_Deallocation be instantiated for the specific type to be deallocated. The generic parameters for this function are the access type and the base type being deallocated.

5.4.  Explicit type conversion

5.4.1.     Only use casting forms: static_cast (excl, void*), dynamic_cast or explicit constructor call

All casts result in some degree of type punning, however, some casts may be considered more error prone than others:
·         It is undefined behavior for the result of a static cast to void* to be cast to any type other than the original from type.
·         Depending on the type of an object, casting away const or volatile and attempting to write to the result is undefined behavior.
·         Casts using reinterpret cast are generally unspecified and/or implementation defined. Use of this cast increases the effort required to reason about the code and reduces its portability.
·         Simplistically, a C-style cast and a non class function style cast can be considered as a sequence of the other cast kinds. Therefore, these casts suffer from the same set of problems. In addition, without a unique syntax, searching for such casts in code is extremely difficult.

Corresponding Ada rule:

Ada uses the term type conversion when converting a value from one type to another type. There is only one syntax for type conversion:  subtype-name (expression-or-identifier)
If the target type of a conversion is an integer type and the operand is a real type then the result is the result rounded to the nearest integer (away from zero if exactly half way between two integers). Range checking is done after the converted value has been calculated.

5.4.2.     Do not cast an expression to an enumeration type

The result of casting an integer to an enumeration type is unspecified if the value is not within the range of the enumeration. This also applies when casting between different enumeration types.
For this reason conversions to an enumeration type should be avoided.

Corresponding Ada rule:

Converting between an integer and an enumeration value is not performed by type conversion. Instead, enumeration types have the attributes T’Pos and T’Val. T’Pos takes an argument of the enumeration type and returns the position number of that argument. T’Val takes an integer as an argument and returns the value of type T whose position number equals the value of the argument. If there is no value with the given position number the exception Constraint_Error is raised.

5.4.3.      Do not convert from a base class to a derived class

The most common reason for casting down an inheritance hierarchy, is to call derived class methods on an object that is a reference or pointer to the base class.
Using a virtual function removes the need for the cast completely and improves the maintainability of the code.

Corresponding Ada rule:

In Ada casting between levels of an inheritance hierarchy is called a view conversion. Converting from a base type to a derived type is valid only when the derived type does not add any data members to the set of members defined in the base type.
In Ada polymorphic types are called tagged types.

5.5.  Multiplicative operators

5.5.1.     Ensure that the right hand operand of the division or remainder operations is non-zero

The result of integer division or remainder operation is undefined if the right hand operand is zero. Therefore, appropriate safeguards should be coded explicitly (or instrumented by a tool) to ensure that division by zero does not occur.

Corresponding Ada rule:

The result of division by zero is well defined in Ada. The result will cause the exception Constraint_Error to be raised.

5.6.  Shift operators

5.6.1.     Do not use bitwise operators with signed operands

Use of signed operands with bitwise operators is in some cases subject to undefined or implementation defined behavior. Therefore, bitwise operators should only be used with operands of unsigned integral types.

Corresponding Ada rule:

In Ada the bitwise operators are only defined for unsigned types.

5.7.  Equality operators

5.7.1.     Do not write code that expects floating point calculations to yield exact results

Floating point calculations suffer from machine precision (epsilon), such that the exact result may not be representable.
Epsilon is defined as the difference between 1 and the smallest value greater than 1 that can be represented in a given floating point type. Therefore, comparisons of floating point values need to take epsilon into account.

Corresponding Ada rule:

Floating point number exhibit the same behavior in Ada. If you want exact equality in a real number then use a fixed point representation instead of a floating point representation.

5.7.2.     Ensure that a pointer to a member that is a virtual function is only compared (==) with nullptr

The result of comparing a pointer to member to a virtual function to anything other than nullptr is unspecified.

Corresponding Ada rule:

Ada polymorphism does not depend upon pointers to members of a class hierarchy. Parameters to subprograms can be defined as class-wide parameters, and the actual parameter can be any object with the class hierarchy.

5.8.  Conditional operator

5.8.1.     Do not use the conditional operator (?:) as a sub-expression

Evaluation of a complex condition is best achieved through explicit conditional statements (if/else). Using the result of the conditional operator as an operand reduces the maintainability of the code.
The only permissible uses of a conditional expression are:
·         argument expression in a function call
·         return expression
·          initializer in a member initialization list
·         object initialize
·         the right hand side operand of assignment (excluding compound assignment)
The last use is allowed on the basis of initialization of an object with automatic storage duration being equivalent to its declaration, followed by assignment.

Corresponding Ada rule:

Ada2012 provides two forms of conditional expression; the conditional if statement and the conditional case statement:
conditional_expression ::= if_expression | case_expression
    if_expression ::= 
   if 
condition then dependent_expression
   {elsif 
condition then dependent_expression}
   [else dependent_
expression]
condition ::= boolean_expression
   case_expression ::= 
    case selecting_
expression is
    
case_expression_alternative {,
    
case_expression_alternative}
case_expression_alternative ::= 
    when 
discrete_choice_list =>
        dependent_
expression
Wherever the Syntax Rules allow an expression, a conditional_expression may be used in place of the expression, so long as it is immediately surrounded by parentheses.
Note: In Ada assignment is not an expression. Each dependent expression of a condtional expression must be the type of the conditional expression.

6.     Statements

6.1.  Selection statements

6.1.1.     Enclose the body of a selection or an iteration statement in a compound statement

Follow each control flow primitive (if, else, while, for, do and switch) by a block enclosed by braces, even if the block is empty or contains only one line. Use of null statements or statement expressions in these contexts reduces code readability and making it harder to maintain.

Corresponding Ada rule:

All Ada selection or iteration statements are always fully bracketed compound statements.

6.1.2.     Explicitly cover all paths through multi-way selection statements

Make sure that each if-else-if chain has a final else clause, and every switch statement has a default clause.
The advantage is that all execution paths are explicitly considered, which in turn helps reduce the risk that an unexpected value will result in incorrect execution.

Corresponding Ada rule:

The rule for if-elsif-else in Ada applies.
Ada case statements require all possible values of the selection type to be covered. If some value or values are not covered explicitly then an others case must be included. The compiler will issue an error if this rule is not followed.

6.1.3.     Ensure that a non-empty case statement block does not fall through to the next label

Fall through from a non empty case block of a switch statement makes it more difficult to reason about the code, and therefore harder to maintain.

Corresponding Ada rule:

Ada case statements do not exhibit fall-through.

6.1.4.     Ensure that a switch statement has at least two case labels, distinct from the default label

A switch statement with fewer than two case labels can be more naturally expressed as a single if statement.

Corresponding Ada rule:

Ada case statements are intended to handle all the values of a discrete type, not just one. A simple if statement is appropriate if only one value of a discrete range of values is being tested.

6.2.  Iteration statements

6.2.1.     Implement a loop that only uses element values as a range-based loop

A range-based for statement reduces the amount of boilerplate code required to maintain correct loop semantics.
A range-based loop can normally replace an explicit loop where the index or iterator is only used for accessing the container value.

Corresponding Ada rule:

The traditional Ada for loop always iterates over a range of values. The Ada iterator loop iterates through the values of all standard Ada containers and all Ada arrays.

6.2.2.     Ensure that a loop has a single loop counter, an optional control variable, and is not degenerate

A loop is considered ’degenerate’ if:
·         when entered, the loop is infinite, or
·         the loop will always terminate after the first iteration.
To improve maintainability it is recommended to avoid degenerate loops and to limit them to a single counter variable.

Corresponding Ada rule:

Ada provides four kinds of loops.
·         The simple loop is the most general loop and can be used for loops that test termination at the beginning of the loop, at the end of the loop, or in the middle of the loop
·         The while loop tests a control expression at the top of the loop
·         The for loop executes for each value in a specified range
·         The iterator loop executes for every member of a container or array
Ada does not provide a direct equivalent of the C++ for loop.

6.2.3.     Do not alter the control or counter variable more than once in a loop

The behavior of iteration statements with multiple modifications of control or counter variables is difficult to understand and maintain.

Corresponding Ada rule:

The Ada for loop has a counter which is modified only at the top of the loop, and is read-only within the body of the loop. The counter cannot be modified more than once per loop iteration.

6.2.4.     Only modify a for loop counter in the for expression

It is expected that a for loop counter is modified for every iteration. To improve code readability and maintainability, the counter variable should be modified in the loop expression.

Corresponding Ada rule:

The Ada for loop has a counter which is modified only at the top of the loop, and is read-only within the body of the loop. The counter cannot be modified more than once per loop iteration.

6.3.  Jump statements

6.3.1.     Ensure that the label(s) for a jump statement or a switch condition appear later, in the same or an enclosing block

Backward jumps and jumps into nested blocks make it more difficult to reason about the flow through the function.
Loops should be the only constructs that perform backward jumps, and the only acceptable use of a goto statement is to jump forward to an enclosing block.
Control can also be transferred forward into a nested block by virtue of a switch label. Unless case and default labels are placed only into the top level compound statement of the switch, the code will be difficult to understand and maintain.

Corresponding Ada rule:

Ada case statements do not allow selection of a value within an if statement.
Ada loops can be labeled, and the loop exit statement in an inner loop can identify the label of an outer loop, allowing a direct exit from the outer loop controlled by a condition in an inner loop. This ability removes the need to set flag variables throughout a nesting of loops with associated testing of the flag. The advantage is code simplicity and added readability.

6.3.2.     Ensure that execution of a function with a non-void return type ends in a return statement with a value

Undefined behavior will occur if execution of a function with a non void return type (other than main) flows off the end of the function without encountering a return statement with a value.
Exception:
The main function is exempt from this rule, as an implicit return 0; will be executed, when an explicit return statement is missing.

Corresponding Ada rule:

Ada provides two kinds of subprograms. Procedures never return a value. Functions always return a value. The return value of a function must always be handled by the calling subprogram. The Ada compiler will issue an error if a function does not return a value of the specified return type, or if a procedure attempts to return a value of any type.
The Ada main subprogram is always a procedure and returns no value.

6.4.  Declaration statement

To preserve locality of reference, variables with automatic storage duration should be defined just before they are needed, preferably with an initializer, and in the smallest block containing all the uses of the variable.
The scope of a variable declared in a for loop initialization statement extends only to the complete for statement.
Therefore, potential use of a control variable outside of the loop is naturally avoided.

Corresponding Ada rule:

Ada only allows variables, constants, subprograms, or tasks to be defined in the declaration section of a block. The one exception is the for loop control variable which is defined at the start of the for loop and is not visible outside the for loop.

7.     Declarations

7.1.  Specifiers

7.1.1.     Declare each identifier on a separate line in a separate declaration

Declaring each variable or typedef on a separate line makes it easier to find the declaration of a particular identifier.
Determining the type of a particular identifier can become confusing for multiple declarations on the same line.
Exception:
For loop initialization statement is exempt from this rule, as in this context the rule conflicts with Rule 6.4.1: ”Postpone variable definitions as long as possible”, which takes precedence.

Corresponding Ada rule:

Ada only allows variables, constants, subprograms, or tasks to be defined in the declaration section of a block. The one exception is the for loop control variable which is defined at the start of the for loop and is not visible outside the for loop.

7.1.2.     Use const whenever possible

This allows specification of semantic constraint which a compiler can enforce. It explicitly communicates to other programmers that value should remain invariant. For example, specify whether a pointer itself is const, the data it points to is const, both or neither.
Exception:
By-value return types are exempt from this rule. These should not be const as doing so will inhibit move semantics.

Corresponding Ada rule:

Ada allows an object to be defined as constant, but does not have the complex const rules of C++. When passing parameters to a subprogram the passing mode can be defined as IN, which forces the subprogram to view that parameter as a constant. Parameters passed with the IN mode are read-only within the subprogram they are passed to.

7.1.3.     Do not place type specifiers before non-type specifiers in  declaration

The C++ Standard allows any order of specifiers in a declaration. However, to improve readability if a non-type specifier (typedef, friend, constexpr, register, static, extern, thread local, mutable, inline, virtual, explicit) appears in a declaration, it should be placed leftmost in the declaration.

Corresponding Ada rule:

Variable declarations always take the form of
object_declaration ::= 
    
defining_identifier_list : [aliased] [constantsubtype_indication [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantaccess_definition [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantarray_type_definition [:= expression]
        [
aspect_specification];
  | 
single_task_declaration
  | 
single_protected_declaration
defining_identifier_list ::= 
  
defining_identifier {, defining_identifier}

7.1.4.     Place CV-qualifiers on the right hand side of the type they apply to

The const or volatile qualifiers can appear either to the right or left of the type they apply to. When the unqualified portion of the type is a typedef name (declared in a previous typedef declaration), placing the CV-qualifiers on the left hand side, may result in confusion over what part of the type the qualification applies to.
For consistency, it is recommended that this rule is applied to all declarations.

Corresponding Ada rule:

Variable declarations always take the form of
object_declaration ::= 
    
defining_identifier_list : [aliased] [constantsubtype_indication [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantaccess_definition [:= expression]
        [
aspect_specification];
  | 
defining_identifier_list : [aliased] [constantarray_type_definition [:= expression]
        [
aspect_specification];
  | 
single_task_declaration
  | 
single_protected_declaration
defining_identifier_list ::= 
  
defining_identifier {, defining_identifier}

7.1.5.     Do not inline large functions

The definition of an inline function needs to be available in every translation unit that uses it. This in turn requires that the definitions of inline functions and types used in the function definition must also be visible.
The inline keyword is just a hint, and compilers in general will only inline a function body if it can be determined that performance will be improved as a result.
As the compiler is unlikely to inline functions that have a large number of statements and expressions, inlining such functions provides no performance benefit but will result in increased dependencies between translation units.
Given an approximate cost of 1 for every expression and statement, the recommended maximum cost for a function is 32.

Corresponding Ada rule:

The inline aspect of a subprogram is optional for a compiler. It notifies the compiler of the programmer’s desire that the subprogram be inlined.

7.1.6.     Use class types or typedefs to abstract scalar quantities and standard integer types

Using class types to represent scalar quantities exploits compiler enforcement of type safety. If this is not possible, typedefs should be used to aid readability of code.
Plain char type should not be used to define a typedef name, unless the type is intended for parameterizing the code for narrow and wide character types. In other cases, an explicit signed char or unsigned char type should be used in a typedef as appropriate.
To enhance portability, instead of using the standard integer types (signed char, short, int, long, long long, and the unsigned counterparts), size specific types should be defined in a project-wide header file, so that the definition can be updated to match a particular platform (16, 32 or 64bit). Where available, intN t and uintN t types (e.g. int8 t) defined in the cstdint header file should be used for this purpose.
Where the auto type specifier is used in a declaration, and the initializer is a constant expression, the declaration should not be allowed to resolve to a standard integer type. The type should be fixed by casting the initializer to a size specific type.
Exception:
The C++ Language Standard places type requirements on certain constructs. In such cases, it is better to use required type explicitly rather than the typedef equivalent which would reduce the portability of the code.
The following constructs are therefore exceptions to this rule:
·         int main()
·         T operator++(int)
·         T operator–(int)

Corresponding Ada rule:

Ada allows the programmer to create simple and detailed definitions of scalar types and subtypes. New integer types can be simply defined by creating a type name and specifying the valid range of values for the type, or they can be derived from an existing type with a restriction on the range of valid values.
For example:
type Counts is range 0..100;
type Normalized is new Integer range -100..100;
Ada also allows the programmer to define subtypes of existing types. Objects of a subtype are members of the base type, usually with a restricted range of valid values.
subtype Natural is Integer range 0..Integer’Last;
subtype Positive is Integer range 1..Integer’Last;
Subtypes can be used in mixed calculations with any other subtypes of the same base type. Objects of different base types cannot be used in an expression without explicit conversion to a common type.

7.1.7.     Use a trailing return type in preference to type disambiguation using typename

When using a trailing return type, lookup for the function return type starts from the same scope as the function declarator. In many cases, this will remove the need to specify a fully qualified return type along with the typename keyword.

Corresponding Ada rule:

Ada has no equivalent of a trailing return type.

7.1.8.     Use auto id = expr when declaring a variable to have the same type as its initializer function call

When declaring a variable that is initialized with a function call, the type is being specified twice. Initially on the return of the function and then in the type of the declaration.
Using auto and implicitly deducing the type of the initializer will ensure that a future change to the declaration of foo will not result in the addition of unexpected implicit conversions.

Corresponding Ada rule:

This rule is contrary to Ada strong typing. Every variable must be declared to be definite, even if it is a member of an indefinite type. The type returned by an initializing function must match the type of the object being declared.
An object of an indefinite type must be initialized to a definite object. For instance, the type String is an unconstrained array of character. An unconstrained array is an indefinite type. Each instance of a String must have a specified length, which is defined when the String object is initialized. The String object is definite. The String type is indefinite.

7.1.9.     Do not explicitly specify the return type of a lambda

Allowing the return type of a lambda to be implicitly deduced reduces the danger of unexpected implicit conversions, as well as simplifying future maintenance, where changes to types used in the lambda would otherwise result in the need to change the return type.

Corresponding Ada rule:

Ada does not allow lambdas.

7.1.10.                        Use static_assert for assertions involving compile time constants

A static assert will generate a compile error if its expression is not true. The earlier that a problem can be diagnosed the better, with the earliest time possible being as the code is written.

Corresponding Ada rule:

When defining a subtype the programmer should use either a Dynamic_Predicate or a Static_Predicate if the use of a simple range cannot fully define the characteristics of the subtype.
A Static_Predicate expression must be one of
·         A static membership test where the choice is selected by the current instance
·         A case expression whose dependent expressions are static and selected by the current instance
·         A call of the predefined operations =, /=, <, <=, >, >= where one operand is the current instance
·         An ordinary static expression
A Dynamic_Expression can be any Boolean expression.
Examples:

subtype Even is Integer
   with Dynamic_Predicate => Even mod 2 = 0;

subtype Letter is Character
   with Static_Predicate => Letter in ‘A’..’Z’|’a’..’z’;

7.2.  Enumeration declarations

7.2.1.     Use an explicit enumeration base and ensure that it is large enough to store all enumerations

The underlying type of an unscoped enumeration is implementation defined, with the only restriction being that the type must be able to represent the enumeration values. An explicit enumeration base should always be specified with a type that will accommodate both the smallest and the largest enumerator.
A scoped enum will implicitly have an underlying type of int, however, the requirement to specify the underlying type still applies.
Exception:
An enumeration declared in an extern "C" block (i.e. one intended to be used with C) does not require an explicit underlying type.

Corresponding Ada rule:

Ada enumerations are not numeric types and cannot be converted to numeric types. The compiler chooses the size of the enumeration representation base upon the number of enumeration values.

7.2.2.     Initialize none, the first only, or all enumerators in an enumeration

It is error prone to initialize explicitly only some enumerators in an enumeration, and to rely on the compiler to initialize the remaining ones. For example, during maintenance it may be possible to introduce implicitly initialized enumerators with the same value as an existing one initialized explicitly.
Exception:
When an enumeration is used to define the size and to index an array, it is acceptable and recommended to define three additional enumerators after all other enumerators, to represent the first and the last elements, and the size of the array.

Corresponding Ada rule:

When specifying the enumeration representation all enumerated values must be specified.
Example:
type Mix_Code is (ADD, SUB, MUL, LDA, STA, STZ);
for Mix_Code use (ADD => 1, SUB => 2, MUL => 3, LDA => 8, STA => 24, STZ =>33);

7.3.  Namespaces

7.3.1.     Do not use using directives

Namespaces are an important tool in separating identifiers and in making interfaces explicit.
A using directive, i.e. using namespace, allows any name to be searched for in the namespace specified by the using directive.
A using declaration, on the other hand, brings in a single name from the namespace, as if it was declared in the scope containing the using declaration.

Corresponding Ada rule:

The unit of encapsulation in Ada is the package. Packages provide the name space designation of C++ namespaces.
In Ada one can either use the entire package, use only a specific type defined in the package, or explicitly append the package name to each element accessed from the package.
Any naming ambiguity will be identified by the compiler.

7.4.  Linkage specifications

7.4.1.     Ensure that any objects, functions or types to be used from a single translation unit are defined in an unnamed namespace in the main source file

Declaring an entity in an unnamed namespace limits its visibility to the current translation unit only. This helps reduce the risk of name clashes and conflicts with declarations in other translation units.
It is preferred to use unnamed namespaces rather than the static keyword to declare such entities.

Corresponding Ada rule:

Each compilation unit specifies its own dependencies in its dependency clauses. There is no visibility to compilation units not defined in the dependency clause.

7.4.2.     Ensure that an inline function, a function template, or a type used from multiple translation units is defined in a single header file

An inline function, a function template or a user defined type that is intended for use in multiple translation units should be defined in a single header file, so that the definition will be processed in exactly the same way (the same sequence of tokens) in each translation unit.
This will ensure that the one definition rule is adhered to, avoiding undefined behavior, as well as improving the maintainability of the code.

Corresponding Ada rule:

Each subprogram, generic compilation unit, type, task, task type, protected object, or protected type is defined uniquely in a compilation unit. Dependency upon compilation units which define types or objects with overlapping identifiers results in ambiguity which is flagged as a compilation error and must be corrected before an executable is produced by the compilation process.

7.4.3.     Ensure that an object or a function used from multiple translation units is declared in a single header file

An object or function with external linkage should be declared in a single header file in the project.
This will ensure that the type seen for an entity in each translation unit is the same thereby avoiding undefined behavior.

Corresponding Ada rule:

Objects used by multiple compilation units must be declared in the public region of a package. Functions and procedures can either be declared in a package or in a stand-alone compilation unit.

7.5.  The asm declaration

7.5.1.     Do not use the asm declaration

Use of inline assembly should be avoided since it restricts the portability of the code.

Corresponding Ada rule:

Machine code insertions should be avoided since they restrict the portability of the code.

8.     Definitions

8.1. Type names

8.1.1.     Do not use multiple levels of pointer indirection

In C++, at most one level of pointer indirection combined with references is sufficient to express any algorithm or API.
Instead of using multidimensional arrays, an array of containers or nested containers should be used. Code reliant on more than one level of pointer indirection will be less readable and more difficult to maintain.

Corresponding Ada rule:

Use of multidimensional arrays is allowed, or one can choose containers or nested containers. Ada arrays do not employ pointer indirection at the source code level.

8.2. Meanings of declarators

8.2.1.     Make parameter names absent or identical in all declarations

Although the C++ Standard does not mandate that parameter names match in all declarations of a function (e.g. a declaration in a header file and the definition in the main source file), it is good practice to follow this principle.

Corresponding Ada rule:

All subprogram parameter names must be declared and must match the corresponding subprogram implementation.
Subprogram parameters may be referenced by position or by name when the subprogram is called. It is recommended to use named notation when calling a subprogram. When using named notation the actual parameter is explicitly matched with the formal parameter, and the order of parameters in the called subprogram is not relevant.
Example:
Procedure Get_Line(Item : out String; Last : out Natural);
Length : Natural;
Input : String(1..256);
Get_Line(Item => Input, Last => Length);

8.2.2.     Do not declare functions with excessive number of parameters

A function defined with a long list of parameters often indicates poor design and is difficult to read and maintain.
The recommended maximum number of function parameters is six.

Corresponding Ada rule:

While it is seldom useful to declare a subprogram with a large number of parameters, the problem is greatly relieved through the use of named notation.

8.2.3.     Pass small objects with a trivial copy constructor by value

Because passing by const reference involves an indirection, it will be less efficient than passing by value for a small object with a trivial copy constructor.

Corresponding Ada rule:

Ada parameters have an associated mode indicating data flow. The modes are IN, OUT, IN OUT. The compiler determines whether a particular parameter should be passed by copy or by reference.

8.2.4.     Do not pass std::unique_ptr by const reference

An object of type std::unique ptr should be passed as a non-const reference, or by value. Passing by non-const reference signifies that the parameter is an in/out parameter. Passing by value signifies that the parameter is a sink (i.e. takes ownership and does not return it).
A const reference std::unique ptr parameter provides no benefits and restricts the potential callers of the function.

Corresponding Ada rule:

Ada parameter modes explicitly state whether the parameter is passed IN, OUT, or IN OUT.

8.3.  Function definitions

8.3.1.     Do not write functions with excessive McCabe Cyclomatic Complexity

The McCabe Cyclomatic Complexity is calculated as the number of decision branches within a function plus 1.
Complex functions are hard to maintain and test effectively. It is recommended that the value of this metric does not exceed 10.

Corresponding Ada rule:

Excessive cyclomatic complexity has been shown to produce code that is difficult to understand and maintain. It is often an indication of poor code design.

8.3.2.     Do not write functions with a high static program path count

Static program path count is the number of non-cyclic execution paths in a function. Functions with a high number of paths through them are difficult to test, maintain and comprehend. The static program path count of a function should not exceed 200.

Corresponding Ada rule:

High static path count is an indication of poor software design. All code should be testable and maintainable.

8.3.3.     Do not use default arguments

Use of default arguments can make code maintenance and refactoring more difficult. Overloaded forwarding functions can be used instead without having to change existing function calls.

Corresponding Ada rule:

Overloaded forwarding functions provide no maintenance benefit. Use of default arguments and named notation for calling subprograms clearly documents all calls for maintenance purposes without creating a plethora of overloaded subprograms.

8.3.4.     Define =delete functions with parameters of rvalue reference to const

A simple model for an rvalue reference is that it allows for the modification of a temporary. A const rvalue reference therefore defeats the purpose of the construct as modifications are not possible.
However, one valid use case is where the function is defined =delete. This will disallow the use of an rvalue as an argument to that function.

Corresponding Ada rule:

Ada has no equivalent to defining a subprogram as =delete.

8.4.  Initializers

8.4.1.     Do not access an invalid object or an object with indeterminate value

A significant component of program correctness is that the program behavior should be deterministic. That is, given the same input and conditions the program will produce the same set of results.
If a program does not have deterministic behavior, then this may indicate that the source code is reliant on unspecified or undefined behavior.
Such behaviors may arise from use of:
·         variables not yet initialized
·         memory (or pointers to memory) that has been freed
·         moved from objects

Corresponding Ada rule:

The Ada compiler identifies when the value of a variable is used before the variable is initialized or assigned a value. The SPARK subset of Ada requires all variables to be fully initialized.

8.4.2.     Ensure that a braced aggregate initialize matches the layout of the aggregate object

If an array or a struct is non-zero initialized, initializers should be provided for all members, with an initializer list for each aggregate (sub)object enclosed in braces. This will make it clear what value each member is initialized with.

Corresponding Ada rule:

Aggregates can be constructed for arrays or records. While aggregates can be constructed using positional notation, similar to C++, named notation should always be used.
Use of named notation will ensure and document that all elements of the composite type have been initialized.

9.     Classes

9.1.  Member functions

9.1.1.     Declare static any member function that does not require this. Alternatively, declare const any member function that does not modify the externally visible state of the object

A non-virtual member function that does not access the this pointer can be declared static. Otherwise, a function that is virtual or does not modify the externally visible state of the object can be declared const.
The C++ language permits that a const member function modifies the program state (e.g. modifies a global variable, or calls a function that does so). However, it is recommended that const member functions are logically const also, and do not cause any side effects.
The mutable keyword can be used to declare member data that can be modified in a const function, however, this should only be used where the member data does not affect the externally visible state of the object.

Corresponding Ada rule:

Packages are the unit of encapsulation in Ada. Tagged types are polymorphic types in Ada. Tagged types can be defined in a package, making them available to multiple compilation units. Tagged types can have primitive operations, which correspond to virtual member functions in C++. A subprogram is primitive to some tagged type T if all of the following are true:
·         The subprogram is declared in the visible part of the package in which tagged type T is declared
·         The subprogram has a parameter of type T, or an access parameter pointing to an instance of type T, or is a function returning a result of type T
Any subprogram which is declared in the package where type T is declared, and does not meet the above requirements, is not a primitive of type T. Such a subprogram is equivalent to a C++ static member function. Any procedure with only an parameter of IN mode of type T is equivalent to a C++ const member function.

9.1.2.     Make default arguments the same or absent when overriding a virtual function

The C++ Language Standard allows that default arguments be different for different overrides of a virtual function.
However, the compiler selects the argument value based on the static type of the object used in the function call.
This can result in confusion where the default argument value used may be different to the expectation of the user.

Corresponding Ada rule:

The Ada Language Standard requires  overridden subprograms be conformant in their use of default parameter expressions. When a subprogram overrides another subprogram the parameter profile cannot change regarding default expressions.

9.1.3.     Do not return non-const handles to class data from const member functions

A pointer or reference to non-const data returned from a const member function may allow the caller to modify the state of the object. This contradicts the intent of a const member function.

Corresponding Ada rule:

A procedure with an IN mode parameter  cannot modify the data passed to it, nor can it return a value of any kind.

9.1.4.     Do not write member functions which return non-const handles to data less accessible than the member function

Member data that is returned by a non-const handle from a more accessible member function, implicitly has the access of the function and not the access it was declared with. This reduces encapsulation and increases coupling.
Exception:
Non-const operator [] is exempt from this rule, as in this context the rule conflicts with Rule 13.2.4: ”When overloading the subscript operator (operator[]) implement both const and non-const versions”, which takes precedence.

Corresponding Ada rule:

Ada functions need not return pointers to complex data. Instead they can return entire complex objects including records and arrays. This avoids the lifetime issues associated with C++ member functions.

9.1.5.     Do not introduce virtual functions in a final class

Declaring a class as final explicitly documents that this is a leaf class as it cannot be used as a base class.
Introducing a virtual function in such a class is therefore redundant as the function can never be overridden in a derived class.

Corresponding Ada rule:

Ada does not explicitly label a tagged type “final”. Instead, if no subprograms with class-wide parameters are introduced the tagged type has the effect of being “final”.

9.2.  Bit-fields

9.2.1.     Declare bit-fields with an explicitly unsigned integral or enumeration type

To avoid reliance on implementation defined behavior, only declare bit-fields of an explicitly unsigned type (uintN t) or an enumeration type with an enumeration base of explicitly unsigned type.

Corresponding Ada rule:

One must define data types with value ranges that can fit into the specified bit layout of a record. Failure to do so will result in a compiler error message.
Example:
Word : constant := 4;  --  storage element is byte, 4 bytes per word
type State         is (A,M,W,P);
type Mode          is (Fix, Dec, Exp, Signif);
type Byte_Mask     is array (0..7)  of Boolean;
type State_Mask    is array (State) of Boolean;
type Mode_Mask     is array (Mode)  of Boolean;
type Program_Status_Word is
  record
      System_Mask        : Byte_Mask;
      Protection_Key     : Integer range 0 .. 3;
      Machine_State      : State_Mask;
      Interrupt_Cause    : Interruption_Code;
      Ilc                : Integer range 0 .. 3;
      Cc                 : Integer range 0 .. 3;
      Program_Mask       : Mode_Mask;
      Inst_Address       : Address;
end record;
for Program_Status_Word use
  record
      System_Mask      at 0*Word range 0  .. 7;
      Protection_Key   at 0*Word range 10 .. 11; --
 bits 8,9 unused
      Machine_State    at 0*Word range 12 .. 15;
      Interrupt_Cause  at 0*Word range 16 .. 31;
      Ilc              at 1*Word range 0  .. 1;  --
 second word
      Cc               at 1*Word range 2  .. 3;
      Program_Mask     at 1*Word range 4  .. 7;
      Inst_Address     at 1*Word range 8  .. 31;
  end record;
for Program_Status_Word'Size use 8*System.Storage_Unit;
for Program_Status_Word'Alignment use 8;

10.           Derived classes

10.1.                 Multiple base classes

10.1.1.                        Ensure that access to base class subobjects does not require explicit disambiguation

A class inherited more than once in a hierarchy, and not inherited virtually in all paths will result in multiple base class subobjects being present in instances of the derived object type.
Such objects require that the developer explicitly select which base class to use when accessing members. The result is a hierarchy that is harder to understand and maintain.

Corresponding Ada rule:

Ada allows multiple inheritance only of interfaces, not of tagged types. All subprograms in interfaces are abstract, corresponding to virtual inheritance of multiple base classes in C++.

10.2.                 Virtual functions

10.2.1.                        Use the override special identifier when overriding a virtual function

The override special identifier is a directive to the compiler to check that the function is overriding a base class member. This will ensure that a change in the signature of the virtual function will generate a compiler error.

Corresponding Ada rule:

An overriding_indicator is used to declare that an operation is intended to override (or not override) an inherited operation. 

Syntax

overriding_indicator ::= [notoverriding

Legality Rules

·        the operation shall be a primitive operation for some type;
·        if the overriding_indicator is overriding, then the operation shall override a homograph at the place of the declaration or body;
·        if the overriding_indicator is not overriding, then the operation shall not override any homograph (at any place). 
In addition to the places where Legality Rules normally apply, these rules also apply in the private part of an instance of a generic unit.

10.3.                 Abstract classes

10.3.1.                        Ensure that a derived class has at most one base class which is not an interface class

An interface class has the following properties:
·         all public functions are pure virtual functions or getters, and
·         there are no public or protected data members, and
·          it contains at most one private data member of integral or enumerated type
Inheriting from two or more base classes that are not interfaces, is rarely correct. It also exposes the derived class to multiple implementations, with the risk that subsequent changes to any of the base classes may invalidate the derived class.
On the other hand. it is reasonable that a concrete class may implement more than one interface.

Corresponding Ada rule:

Ada only allows inheritance from one base class. Ada allows multiple inheritance of interfaces.

11.           Member access control

11.1.                 Access specifiers

11.1.1.                        Declare all data member private

If direct access to the object state is allowed through public or protected member data, encapsulation is reduced making the code harder to maintain.
By implementing a class interface with member functions only, precise control is achieved over modifications to object state as well as allowing for pre and post conditions to be checked when accessing data.

Corresponding Ada rule:

There are many uses for packages. One use is to define commonly used constants, while another is to define abstract data types. It is good to use data hiding for abstract data types, but it is also important to openly share commonly used constants. When declaring abstract data types in Ada it is advised that the public view of the data type refers to a private definition of the data structure.

11.2.                 Friends

11.2.1.                        Do not use friend declarations

Friend declarations reduce encapsulation, resulting in code that is harder to maintain.

Corresponding Ada rule:

Ada provides child packages rather than friends. Child packages do not reduce encapsulation. Child packages allow the creation of package extensions without compromising or altering the code in the parent package.

12.           Special member functions

12.1.                 Conversions

12.1.1.                        Do not declare implicit user defined conversions

A user defined conversions can occur through the use of a conversion operator or a conversion constructor (a constructor that accepts a single argument).
A compiler can invoke a single user defined conversion in a standard conversion sequence, but only if the operator or constructor is declared without the explicit keyword.
It is better to declare all conversion constructors and operators explicit.

Corresponding Ada rule:

Ada does not perform implicit conversions.

12.2.                 Destructors

12.2.1.                        Declare virtual, private, or protected the destructor of a type used as a base class

If an object will ever be destroyed through a pointer to its base class, then the destructor in the base class should be virtual. If the base class destructor is not virtual, then the destructors for derived classes will not be invoked.
Where an object will not be deleted via a pointer to its base, then the destructor should be declared with protected or private access. This will result in a compile error should an attempt be made to delete the object incorrectly.

Corresponding Ada rule:

Ada controlled types allow user-defined finalization of objects of the type. Finalization is called when an object goes out of scope.
Ada polymorphism does not require the use of pointers to a base class.

12.3.                 Free store

12.3.1.                        Correctly declare overloads for operator new and delete

operator new and operator delete should work together. Overloading operator new means that a custom memory management scheme is in operation for a particular class or program. If a corresponding operator delete (plain or array) is not provided the memory management scheme is incomplete.
Additionally, if initialization of the allocated object fails with an exception, the C++ runtime will try to call an operator delete with identical parameters as the called operator new, except for the first parameter. If no such operator delete can be found, the memory will not be freed. If this operator delete does not actually need to perform any bookkeeping, one with an empty body should be defined to document this in the code.
When declared in a class, operator new and operator delete are implicitly static members; explicitly including the static specifier in their declarations helps to document this.

Corresponding Ada rule:

When creating a custom storage pool one must override the abstract type Root_Storage_Pool, including overriding its Allocate and Deallocate procedures and its Storage_Size function. There is no way to implement a custom storage pool without implementing all three subprograms.

12.4.                 Initializing bases and members

12.4.1.                        Do not use the dynamic type of an object unless the object is fully constructed

Expressions involving:
·         a call to a virtual member function,
·         use of typeid, or
·         a cast to a derived type using dynamic cast
are said to use the dynamic type of the object.
Special semantics apply when using the dynamic type of an object while it is being constructed or destructed. Moreover, it is undefined behavior if the static type of the operand is not (or is not a pointer to) the constructor’s or destructor’s class or one of its base classes.
In order to avoid misconceptions and potential undefined behavior, such expressions should not be used while the object is being constructed or destructed.

Corresponding Ada rule:

Ada has no concept of a dynamic type, only dynamic type identification. Ada can perform view conversions of a child type to its parent type, but that does not change the type of an object.

12.4.2.                        Ensure that a constructor initializes explicitly all base classes and non-static data members

A constructor should completely initialize its object. Explicit initialization reduces the risk of an invalid state after successful construction. All virtual base classes and direct non-virtual base classes should be included in the initialization list for the constructor. A copy or move constructor should initialize each non-static data member in the initialization list, or if this is not possible then in constructor body. For other constructors, each non-static data member should be initialized in the following way, in order of preference:
·         non static data member initializer (NSDMI), or
·         in initialization list, or
·         in constructor body.
For many constructors this means that the body becomes an empty block.

Corresponding Ada rule:

Ada records, including tagged records, can be defined with default values for all their record components. The default initialization of a parent tagged record is applied to the child tagged record so that the child need not explicitly call parent initialization functions.

12.4.3.                        Do not specify both an NSDMI and a member initialize for the same non-static member

NSDMI stands for ’non static data member initializer’. This syntax, introduced in the 2011 C++ Language Standard, allows for the initializer of a member to be specified along with the declaration of the member in the class body. To avoid confusion as to the value of the initializer actually used, if a member has an NSDMI then it should not subsequently be initialized in the member initialization list of a constructor.

Corresponding Ada rule:

Ada does not provide for multiple initialization of an object.

12.4.4.                        Write members in an initialization list in the order in which they are declared

Regardless of the order of member initializers in a initialization list, the order of initialization is always:
·         Virtual base classes in depth and left to right order of the inheritance graph.
·         Direct non-virtual base classes in left to right order of inheritance list.
·         Non-static member data in order of declaration in the class definition.
To avoid confusion and possible use of uninitialized data members, it is recommended that the initialization list matches the actual initialization order.

Corresponding Ada rule:

Since Ada only allows single inheritance from a base class there is no issue about which base class is first initialized.

12.4.5.                        Use delegating constructors to reduce code duplication

Delegating constructors can help reduce code duplication by performing initialization in a single constructor. Using delegating constructors also removes a potential performance penalty with using an ’init’ method, where initialization for some members occurs twice.

Corresponding Ada rule:

Ada does not provide explicit constructors. Ada does provide an Initialize procedure for controlled types.

12.5.                 Copying and moving class objects

12.5.1.                        Define explicitly =default or =delete implicit special member functions of concrete classes

A compiler may provide some or all of the following special member functions:
·         Destructor
·         Copy constructor
·         Copy assignment operator
·         Move constructor
·         Move assignment operator
The set of functions implicitly provided depends on the special member functions that have been declared by the user and also the special members of base classes and member objects.
The compiler generated versions of these functions perform a bitwise or shallow copy, which may not be the correct copy semantics for the class. It is also not clear to clients of the class if these functions can be used or not.
To resolve this, the functions should be defined with =delete or =default thereby fully documenting the class interface.
Note: As this rule is limited to concrete classes, it is the responsibility of the most derived class to ensure that the object has correct copy semantics for itself and for its sub-objects.

Corresponding Ada rule:

Tagged types inheriting from Controlled types must define Initialize, Adjust, and Finalize procedures to handle initialization, copy semantics, and deletion semantics.

12.5.2.                        Define special members =default if the behavior is equivalent

Corresponding Ada rule:

This rule has no Ada equivalent because Ada prohibits inheritance from multiple base classes.

12.5.3.                        Ensure that a user defined move/copy constructor only moves/copies base and member objects

The human clients of a class will expect that the copy constructor can be used to correctly copy an object of class type. Similarly, they will expect that the move constructor correctly moves an object of class type.
Similarly, a compiler has explicit permission in the C++ Standard to remove unnecessary copies or moves, on the basis that these functions have no other side-effects other than to copy or move all bases and members.

Corresponding Ada rule:

Tagged types inheriting from Controlled types must define Initialize, Adjust, and Finalize procedures to handle initialization, copy semantics, and deletion semantics.

12.5.4.                        Declare noexcept the move constructor and move assignment operator

A class provides the Strong Exception Guarantee if after an exception occurs, the objects maintain their original values.
The move members of a class explicitly change the state of their argument. Should an exception be thrown after some members have been moved, then the Strong Exception Guarantee may no longer hold as the from object has been modified.
It is especially important to use noexcept for types that are intended to be used with the standard library containers.
If the move constructor for an element type in a container is not noexcept then the container will use the copy constructor rather than the move constructor.

Corresponding Ada rule:

Ada has no equivalent to a move constructor. A move constructor moves ownership of data from one object to another. If the resource is accessed through an access value then the access value of the data in the starting object must be copied to the corresponding access field of the target object. After the access value is copied the access value in the starting object must be set to null.

12.5.5.                        Correctly reset moved-from handles to resources in the move constructor

The move constructor moves the ownership of data from one object to another. Once a resource has been moved to a new object, it is important that the moved-from object has its handles set to a default value. This will ensure that the moved-from object will not attempt to destroy resources that it no longer manages on its destruction.
The most common example of this is to assign nullptr to pointer members.

Corresponding Ada rule:

Ada has no equivalent to a move constructor. A move constructor moves ownership of data from one object to another. If the resource is accessed through an access value then the access value of the data in the starting object must be copied to the corresponding access field of the target object. After the access value is copied the access value in the starting object must be set to null.

12.5.6.                        Use and atomic, non-throwing swap operation to implement the copy and move assignment operators

Implementing the copy assignment operator using a non throwing swap provides the Strong Exception Guarantee for the operations.
In addition, the implementation of each assignment operator is simplified without requiring a check for assignment to self.

Corresponding Ada rule:

Use of an atomic swap operation assumes the use of pointers and not full objects. This assumption frequently fails since Ada does not require the use of pointers to complex data types.

12.5.7.                        Declare assignment operators with the ref-qualifier &

In the 2003 C++ Language Standard, user declared types differed from built-in types in that it was possible to have a ’modifiable rvalue’.
The 2011 C++ Language Standard allows for a function to be declared with a reference qualifier. Adding & to the function declaration ensures that the call can only be made on lvalue objects, as is the case for the built-in operators.

Corresponding Ada rule:

Ada does not allow modifiable rvalues.

12.5.8.                        Make the copy assignment operator of an abstract class protected ore define it =delete

An instance of an abstract class can only exist as a subobject for a derived type. A public copy assignment operator would allow for incorrect partial assignments to occur.
The copy assignment operator should be protected, or alternatively defined =delete if copying is to be prohibited in this class hierarchy.

Corresponding Ada rule:

Ada does not allow instances of abstract types. Each instance must be a concrete type derived from the abstract type.

13.           Overloading

13.1.                 Overload resolution

13.1.1.                        Ensure that all overloads of functions are visible from where it is called

When a member function is overridden or overloaded in a derived class, other base class functions of that name will be hidden. A call to a function from the derived class may therefore result in a different function being called than if the same call had taken place from the base class.
To avoid this situation, hidden names should be introduced into the derived class through a using declaration.
A using declaration for a namespace scope identifier, only brings into the current scope the prior declarations of this identifier, and not any declarations subsequently added to the namespace. This too may lead to unexpected results for calls to overloaded functions.

Corresponding Ada rule:

If the programmer wants the base type version of an overloaded or overridden subprogram to be called then the object must be passed to the overloaded subprogram name in the form of a view conversion to the base type.

13.1.2.                        If a member of a set of callable functions includes a universal reference parameter, ensure that one appears in the same position for all other members

A callable function is one which can be called with the supplied arguments. In the C++ Language Standard, this is known as the set of viable functions.
A template parameter declared T&& has special rules during type deduction depending on the value category of the argument to the function call. Scott Meyers has named this a ’Universal Reference’.
As a universal reference will deduce perfectly for any type, overloading them can easily lead to confusion as to which function has been selected.
Exception:
Standard C++ allows for a member of the viable function set to be deleted. In such cases, should these functions be called then it will result in a compiler error.

Corresponding Ada rule:

Ada does not provide an equivalent to universal reference. The closest Ada comes is an class-wide access type, which can reference any object in the inheritance hierarchy rooted at the tagged type specified in the definition of the access type.

13.2.                 Overloaded operators

13.2.1.                        Do not overload operators with special semantics

Overloaded operators are just functions, so the order of evaluation of their arguments is unspecified. This is contrary to the special semantics of the following built-in operators:
·         && – left to right and potentially evaluated
·          || – left to right and potentially evaluated
·          , – left to right
Providing user declared versions of these operators may lead to code that has unexpected behavior and is therefore harder to maintain.
Additionally, overloading the unary & (address of) operator will result in undefined behavior if the operator is used from a location in the source where the user provided overload is not visible.

Corresponding Ada rule:

The C++ operators && and || correspond to the Ada logical expressions “and then” and “or else”. Those logical expressions cannot be overloaded in Ada. Ada has no “address of” operator. It does have an attribute ‘Access which cannot be overloaded.

13.2.2.                        Ensure that the return type of an overloaded binary operator matches the built-in counterparts

Built-in binary arithmetic and bitwise operators return a pure rvalue (which cannot be modified), this should be mirrored by the overloaded versions of these operators. For this reason the only acceptable return type is a fundamental or an enumerated type or a class type with a reference qualified assignment operator.
Built-in equality and relational operators return a boolean value, and so should the overloaded counterparts.

Corresponding Ada rule:

Custom operators can be defined which return complex values. For instance:
type Matrix is array(Natural range 1..10, Natural range 1..10) of float;
type Vector is array(Natural range 1..10) of float;
function “+” (Left, Right : Matrix) return Matrix;
function “+” (Left, Right : Vector) return Vector;
In the case above addition operators are declared for types Matrix and Vector. In this case the acceptable return type is not a scalar and need not be a tagged type.

13.2.3.                        Declare binary arithmetic and bitwise operators as non-members

Overloaded binary arithmetic and bitwise operators should be non-members to allow for operands of different types, e.g. a fundamental type and a class type, or two unrelated class types.

Corresponding Ada rule:

Overloaded binary arithmetic operators should be declared within the package that a particular numeric type is declared. Integer or real types are often defined as part of a larger abstraction such as date and time utilities. Often achieving the same effect in C++ may take the creation of several classes.

13.2.4.                        When overloading the subscript operator (operator[]) implement both const and non-const versions

A non-const overload of the subscript operator should allow an object to be modified, i.e. should return a reference to member data. The const version is there to allow the operator to be invoked on a const object.

Corresponding Ada rule:

There is no subscript operator in Ada. Ada requires the programmer to specify the scalar subtype used to index an array type. Array indices in Ada may be indexed by any scalar type (signed integer, modular integer, enumeration) and the lowest index value may be set to any valid value of the specified index subtype.
Examples:
Type Days is (Mon, Tues, Wed, Thu, Fri, Sat, Sun);
Type Weekly_Sales is array(Days) of float;
This array’s index values start at Mon and end at Sun. Each array element is a float value.

Type Mod_Index is mod 10;
Type Circular_Array is array(Mod_Index) of message;
This array is indexed with a modular type. Modular types exhibit wrap around arithmetic, allowing this array to easily implement a circular message buffer.

Subtype Normalized_Index is Integer range -100..100;
Type Normal_Distribution is array(Normalized_Index) of Count;
This array can be used to count the frequency of some event plotted to a normal curve.
Ada does allow indexing operators to be defined for tagged types. This indexing ability in Ada helps greatly in the implementation of indexable container types such as maps and sets. The container type can be designated to employ either constant indexing or variable indexing. The aspect of the type declaring either constant indexing or variable indexing must indicate one or more functions taking two parameters, one of which is an instance of the container type or a reference to a container instance.
Ada user defined indexing is not used for array types, which are not tagged types.

13.2.5.                        Implement a minimal set of operators and use them to implement all other related operators

In order to limit duplication of code and associated maintenance overheads, certain operators can be implemented in terms of other operators.

Corresponding Ada rule:

The Ada 2012 Language Reference Manual states that one is not allowed to overload “/=” directly, however overloading “=” implicitly overloads “/=”, since not equals is simply the complement of the “=” function.

14.           Templates

14.1.                 Template declarations

14.1.1.                        Using variadic templates rather than ellipsis

Use of the ellipsis notation ... to indicate an unspecified number of arguments should be avoided. Variadic templates offer a type-safe alternative.

Corresponding Ada rule:

The Ada equivalent to a template is called a generic. A programmer can define a generic subprogram or a generic package. Each generic unit definition includes a set of generic parameters followed by a normal subprogram or package specification.
There is no need for an equivalent to C++ variadic parameter lists.
Generic units are explained in section 12 of the Ada 2012 Language Reference Manual.
Generic parameters can be:
·         Formal objects
·         Formal private types and derived types
·         Formal scalar types
·         Formal array types
·         Formal access types
·         Formal subprograms
·         Formal packages
Example of a generic package:
generic
   Size : Positive;
   type Item is private;
package Stack is
   procedure Push(E : in  Item);
   procedure Pop (E : out Item);
   Overflow, Underflow : exception;
end Stack;

package body Stack is

   type Table is array (Positive range <>) of Item;
   Space : Table(1 .. Size);
   Index : Natural := 0;

   procedure Push(E : in Item) is
   begin
      if Index >= Size then
         raise Overflow;
      end if;
      Index := Index + 1;
      Space(Index) := E;
   end Push;

   procedure Pop(E : out Item) is
   begin
      if Index = 0 then
         raise Underflow;
      end if;
      E := Space(Index);
      Index := Index - 1;
   end Pop;

end Stack;
This example implements a generic stack. The package specification begins with  the reserved word “generic” followed by the list of generic parameters. In this case the parameter Size is a generic formal object of the subtype Positive, and the type Item is any non-limited type, private or public.
The package specification only contains two procedures, Push and Pop, plus the definition of two exceptions, Overflow and Underflow. This package implements a singleton stack, therefore no stack type is publicly exposed.
The package body contains the implementation of the Push and Pop procedures plus the definition of the singleton stack object.

14.2.                 Template instantiation and specialization

14.2.1.                        Declare template specializations in the same file as the primary template they specialize

Partial and explicit specializations of function and class templates should be declared with the primary template.
This will ensure that implicit specializations will only occur when there is no explicit declaration available.

Corresponding Ada rule:

Ada generics do not undergo specialization, only instantiation. Examples of instantiation of the generic singleton stack package shown above are:
Package Stack_Int is new Stack(Size => 200, Item => Integer);
Package Stack_Bool is new Stack(100, Boolean);
Note that the generic parameters can be passed by name or by position. After the instantiations above the package procedures can be called as follows:
Stack_Int.Push(N);
Stack_Bool.Push(True);
Note that Stack_Int is a singleton stack containing Integer values while Stack_Bool is a distinct and separate singleton stack containing Boolean values.

14.2.2.                        Do not explicitly specialize a function template that is overloaded with other templates

Overload resolution does not take into account explicit specializations of function templates. Only after overload resolution has chosen a function template will any explicit specializations be considered.

Corresponding Ada rule:

Ada generic formal parameters are much richer and more explicit than C++ template parameters rendering specialization unnecessary.
Use of a formal package parameter in a generic parameter list does not necessarily cause overloading of subprograms.

14.2.3.                        Declare extern an explicitly instantiated template

Declaring the template with extern will disable implicit instantiation of the template when it is used in other translation units, saving time and reducing compile time dependencies.

Corresponding Ada rule:

Ada generics are never implicitly instantiated.

15.           Exception handling

15.1.                 Throwing an exception

15.1.1.                        Only use instances of std::exception for exceptions

Exceptions pass information up the call stack to a point where error handling can be performed. If an object of class type is thrown, the class type itself serves to document the cause of an exception.
Only types that inherit from std::exception, should be thrown.

Corresponding Ada rule:

Only instances of the pre-defined type Exception can be raised. Each instance of exception that is raised can be accompanied by exception information in the form of a string.

15.2.                 Constructors and destructors

15.2.1.                        Do not throw an exception from a destructor

The 2011 C++ Language Standard states that unless a user provided destructor has an explicit exception specification, one will be added implicitly, matching the one that an implicit destructor for the type would have received.
Furthermore when an exception is thrown, stack unwinding will call the destructors of all objects with automatic storage duration still in scope up to the location where the exception is eventually caught.
The program will immediately terminate should another exception be thrown from a destructor of one of these objects.

Corresponding Ada rule:

Ada has no explicit constructors or destructors. Instead, the procedures Initialize and Finalize are called. When a programmer customizes Initialize and Finalize care must be taken not to raise exceptions.

15.3.                 Handling an exception

15.3.1.                        Do not access non-static members from a catch handler of constructor/destructor function try block

When a constructor or a destructor has a function try block, accessing a non-static member from an associated exception handler will result in undefined behavior.

Corresponding Ada rule:

The response to one or more exceptions is specified by an exception_handler.

Syntax

handled_sequence_of_statements ::= 
     
sequence_of_statements
  [exception
     
exception_handler
    {
exception_handler}]
exception_handler ::= 
  when [
choice_parameter_specification:] exception_choice {| exception_choice} =>
     
sequence_of_statements
choice_parameter_specification ::= defining_identifier
exception_choice ::= exception_name | others
Example of an exception handler: 
begin
   Open(File, In_File, "input.txt"); 
exception
   when E : Name_Error =>
      Put("Cannot open input file : ");
      Put_Line(Exception_Message(E));
      raise;
end;

15.3.2.                        Ensure that a program does not result in a call to std::terminate

The path of an exception should be logical and well defined. Throwing an exception that is never subsequently caught, or attempting to rethrow when an exception is not being handled is an indicator of a problem with the design.

Corresponding Ada rule:

Ada exceptions should be handled wherever possible. It is considered bad design to use exceptions as a normal path to program termination.

16.           Preprocessing

16.1.                 Source file inclusion

16.1.1.                        Use the preprocessor only for implementing include guards, and including header files with include guards

The preprocessor should only be used for including header files into other headers or the main source file, in order to form a translation unit. In particular only the following include directive forms should be used:
·         #include <xyz>
·         #include "xyz"

Corresponding Ada rule:

Ada does not require the use of a preprocessor. File dependencies are established through the use of a dependency clause (with clause).
Dependency clauses do not require any equivalent of include guards. Dependency clauses cannot create macros.

16.1.2.                        Do not include a path specifier in filenames supplied in #include directives

Hardcoding the path to a header file in a #include directive may necessitate changes to source code when it is reused or ported.
Alternatively, the directory containing the header file should be passed to the compiler on command line (e.g. –I or /i option).

Corresponding Ada rule:

Ada dependency clauses do not contain filenames. They contain the names of compilation units.

16.1.3.                        Match the filename in a #include directive to the one on the filesystem

Some operating systems have case insensitive filesystems. Code initially developed on such a system may not compile successfully when ported to a case sensitive filesystem.

Corresponding Ada rule:

Ada dependency clauses do not contain filenames. They contain the names of compilation units.

16.1.4.                        Use <> brackets for system and standard library headers. Use quotes for all other headers.

It is common practice that #include <...> is used for compiler provided headers, and #include "..." for user provided files.
Adhering to this guideline therefore helps with the understandability and maintainability of the code.

Corresponding Ada rule:

Ada dependency clauses do not contain filenames. They contain the names of compilation units.

16.1.5.                        Include directly the minimum number of headers required for compilation

Presence of spurious include directives can considerably slow down compilation of a large code base. When a source file is refactored, the list of included headers should be reviewed, to remove include directives which are no longer needed.
Doing so may also offer an opportunity to delete from code repository source files that are no longer used in the project, therefore reducing the level of technical debt.

Corresponding Ada rule:

It is a good practice to minimize the dependency list for an Ada compilation unit. Minimized dependencies support code readability and maintenance.

17.           Standard library

17.1.                 General

17.1.1.                        Do not use std::vector<bool>

The std::vector<bool> specialization does not conform to the requirements of a container and does not work as expected in all STL algorithms.
In particular &v[0] does not return a contiguous array of elements as it does for other vector types. Additionally, the C++ Language Standard guarantees that different elements of an STL container can safely be modified concurrently, except for a container of std::vector<bool> type.

Corresponding Ada rule:

There is no corresponding prohibition for Ada standard libraries.

17.2.                 The C standard library

17.2.1.                        Wrap use of the C Standard Library

The C11 standard library, which is included in the C++ standard library, leaves the handling of concerns relating to security and concurrency up to the developer.
Therefore, if the C standard library is to be used, it should be wrapped, with the wrappers ensuring that undefined behavior and data races will not occur.

Corresponding Ada rule:

The C standard library is not included in the Ada 2012 standard library.

17.3.                 General utilities library

17.3.1.                        Do not use std::move on objects declared with const or const & type

An object with const or const & type will never actually be moved as a result of calling std::move.

Corresponding Ada rule:

There is no operation equivalent to C++ std::move defined in the Ada 2012 standard.

17.3.2.                        Use std::forward to forward universal references

The std::forward function takes the value category of universal reference parameters into account when passing arguments through to callees.
When passing a non universal reference argument std::move should be used.
Note: As auto is implemented with argument deduction rules, an object declared with auto && is also a universal reference for the purposes of this rule.

Corresponding Ada rule:

There are no Ada library components needed to pass parameter values to subprograms.

17.3.3.                        Do not subsequently use the argument to std::forward

Depending on the value category of arguments used in the call of the function, std::forward may or may not result in a move of the parameter.
When the value category of the parameter is an lvalue, then modifications to the parameter will affect the argument of the caller. In the case of an rvalue, the value should be considered as being indeterminate after the call to std::forward (See Rule 8.4.1: ”Do not access an invalid object or an object with indeterminate value”).

Corresponding Ada rule:

There are no Ada library components needed to pass parameter values to subprograms.

17.3.4.                        Do not create smart pointers of an array type

Memory allocated with array new must be deallocated with array delete. A smart pointer that refers to an array object must have this information passed in when the object is created. A consequence of this is that it is not possible to construct such a smart pointer using std::make shared.
A std::array or std::vector can be used in place of the raw array type. The usage and performance will be very similar but will not have the additional complexity required when deallocating the array object.

Corresponding Ada rule:

There is not special complexity dealing with allocating or deallocating arrays in Ada.

17.3.5.                        Do not create an rvalue reference of std::array

The std::array class is a wrapper for a C style array. The cost of moving std::array is linear with each element of the array being moved. In most cases, passing the array by & or const & will provide the required semantics without this cost.

Corresponding Ada rule:

There is no standard wrapper for Ada arrays. The cost of assigning an array or an array slice is equivalent to a memory copy in Ada.

17.4.                 Containers library

17.4.1.                        Use const container calls when result is immediately converted to a const iterator

The 2011 C++ Language Standard introduced named accessors for returning const iterators. Using these members removes an implicit conversion from iterator to const iterator.
Another benefit is that the declaration of the iterator object can then be changed to use auto without the danger of affecting program semantics.

Corresponding Ada rule:

Ada container packages define cursors to traverse a container objects. The cursor type is a private type defined in each standard container package.

17.4.2.                        Use API calls that construct objects in place

The 2011 C++ Language Standard allows for perfect forwarding. This allows for the arguments to a constructor to be passed through an API and therefore allowing for the final object to be constructed directly where it is intended to be used.

Corresponding Ada rule:

Parameter passing needs no special passing helpers.

17.5.                 Algorithms library

17.5.1.                        Do not ignore the result of std::remove, std::remove_if or std::unique

The mutating algorithms std::remove, std::remove if and both overloads of std::unique operate by swapping or moving elements of the range they are operating over.
On completion, they return an iterator to the last valid element. In the majority of cases the correct behavior is to use this result as the first operand in a call to std::erase.

Corresponding Ada rule:

While Ada does not provide an equivalent to these functions as separate library components, the concept of ignoring the return value of a function is foreign to Ada. All function return values must be used as an rvalue to some expression.

18.           Concurrency

18.1.                 General

18.1.1.                        Do not use platform specific multi-threading facilities

Rather than using platform-specific facilities, the C++ standard library should be used as it is platform independent.

Corresponding Ada rule:

Ada tasking is platform independent. There are no platform specific multi-threading facilities written with an Ada API.

18.2.                 Threads

18.2.1.                        Use high_intergrity::thread in place of std::thread

The destructor of std::thread will call std::terminate if the thread owned by the class is still joinable. By using a wrapper class a default behavior can be provided.

Corresponding Ada rule:

Ada tasks do not have an explicit join command nor an explicit detach command.
Ada tasks do have a dependency hierarchy. Each task (other than the environment task) depends on one or more masters. A task is said to be completed when the execution of the corresponding task body is completed. A task is said to be terminated when any finalization of the task body has been performed. The first step in finalizing a master is to wait for the termination of any tasks dependent upon the master. The task executing the master is blocked until all dependents have terminated. Any remaining finalization is then performed and the master is left.

18.2.2.                        Synchronize access to data shared between threads using a single lock

Using the same lock when accessing shared data makes it easier to verify the absence of problematic race conditions.
To help achieve this goal, access to data should be encapsulated such that it is not possible to read or write to the variable without acquiring the appropriate lock. This will also help limit the amount of code executed in the scope of the lock.
Note: Data may be referenced by more than one variable, therefore this requirement applies to the complete set of variables that could refer to the data.
Special attention needs to be made for const objects. The standard library expects operations on const objects to be thread-safe. Failing to ensure that this expectation is fulfilled may lead to problematic data races and undefined behavior. Therefore, operations on const objects of user defined types should consist of either reads entirely or internally synchronized writes.

Corresponding Ada rule:

Ada provides two forms of synchronization for passing data between tasks.
The Rendezvous mechanism provides a means to synchronously pass data directly between two tasks.
The Protected Object provides a way to pass data between tasks through a shared buffer. Protected objects are allowed to have a combination of three kinds of methods.
Protected procedures allow data in the protected object to be modified or updated unconditionally. Protected procedures implicitly manipulate a read/write lock on the protected object. Protected entries allow data in the protected object to be modified or updated conditionally.
 Protected entries have a boundary condition which must be satisfied. When the boundary condition evaluates to False the protected entry is blocked and the calling task is suspended and placed in an entry queue. Protected entries automatically manipulate a read/write lock on the protected object. Protected functions are only allowed read access to the protected object. Protected functions may not modify or update the state of the protected object. Protected functions automatically manipulate a shared read lock on the protected object allowing multiple tasks to read from the protected object simultaneously.

18.2.3.                        Do not share volatile data between threads

Declaring a variable with the volatile keyword does not provide any of the required synchronization guarantees:
·         Atomicity
·         Visibility
·          Ordering
Use mutex locks or ordered atomic variables, to safely communicate between threads and to prevent the compiler from optimizing the code incorrectly.

Corresponding Ada rule:

Use the Rendevous or protected objects to communicate between tasks.

18.2.4.                        Use the std::call_once rather than the Double_Checked Locking pattern

The Double-Checked Locking pattern can be used to correctly synchronize initializations.
However, the C++ standard library provides std::call_once which allow for a cleaner implementation.
Initialization of a local object with static storage duration is guaranteed by the C++ Language Standard to be reentrant.
However this conflicts with Rule 3.3.1: ”Do not use variables with static storage duration”, which takes precedence.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.                 Mutual exclusion

18.3.1.                        Within the scope of a lock, ensure that no static path results in a lock of the same mutex

It is undefined behavior if a thread tries to lock a std::mutex it already owns, this should therefore be avoided.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.2.                        Ensure that order of nesting of locks in a project forms a DAG

Mutex locks are a common causes of deadlocks. Multiple threads trying to acquire the same lock but in a different order may end up blocking each other.
When each lock operation is treated as a vertex, two consecutive vertices with no intervening lock operation in the source code are considered to be connected by a directed edge. The resulting graph should have no cycles, i.e. it should be a Directed Acyclic Graph (DAG).

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.3.                        Do not use stfd::recursive_mutex

Use of std::recursive mutex is indicative of bad design: Some functionality is expecting the state to be consistent which may not be a correct assumption since the mutex protecting a resource is already locked.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.4.                        Only use std::unique_lock when std::lock_guard cannot be used

The std::unique lock type provides additional features not available in std::lock guard. There is an additional cost when using std::unique lock and so it should only be used if the additional functionality is required.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.3.5.                        Do not use the members of std::mutex directly

A mutex object should only be managed by the std::lock guard or std::unique lock object that owns it.

18.3.6.                        Do not use relaxed atomics

Using non-sequentially consistent memory ordering for atomics allows the CPU to reorder memory operations resulting in a lack of total ordering of events across threads. This makes it extremely difficult to reason about the correctness of the code.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.

18.4.                 Condition variables

18.4.1.                        Do not use std::condition_variable_any on a std::mutex

When using std::condition variable any, there is potential for additional costs in terms of size, performance or operating system resources, because it is more general than std::condition variable.
std::condition variable works with std::unique lock<std::mutex>, while std::condition variable any can operate on any objects that have lock and unlock member functions.

Corresponding Ada rule:

Allow the Ada tasking mechanisms to perform implicit lock manipulations.


Comments

  1. 1.3.5: use 'raise Exception_Name with "string";' - much simpler!

    ReplyDelete
  2. I appreciate the detailed comparison. Thank you!

    ReplyDelete
  3. I wrote a reddit post discussing some details of this post: https://www.reddit.com/r/ada/comments/61n91i/comparing_ada_and_high_integrity_c/

    ReplyDelete
  4. Ad 13.2.4: There is a user defined "indexing" for tagged types (see Ada 2012 RM, section 4.1.6). It doesn't make the type an array though. It is even possible to "index" using non-scalar types; e.g. strings, which is useful for maps.

    ReplyDelete
    Replies
    1. Thank you. I have updated the comparison to include user defined indexing for tagged container types.

      Delete
  5. His article is very useful, interesting and easy to read and understand, hopefully useful

    ReplyDelete

Post a Comment

Popular posts from this blog

An Ada Publish-Subscribe Producer-Consumer Exercise

Ada vs C++ Bit-fields