The vulnerability as described in ISO/IEC TR 24772-1 clause 6.40 exists in C++. C++ provides the facility Templates to support the generic programming methodology.
C++ provides templates for functions, classes(types), and variables (constants). In addition one can form alias templates for class templates. Template parameters can be types, values (including addresses of global variables), and templates. C++ Templates can have variadic template parameters, that mean any number of arguments of a given kind can be used. Concepts are templates that describe constraints on template arguments and can be used to define template parameters or other deduced contexts.
At compile-time, templates are instantiated with concrete template arguments. Function templates as well as class template constructors can deduce the concrete template argument from the types of the function arguments used in a call. For class templates in addition to the implicit deduction guides provided by its constructors, explicit deduction guides can be specified. This mechanism of template-argument deduction allows one to use templates without explicitly mentioning a template argument for each template parameter. For class templates, only those member functions get instantiated that are actually used. Each template instantiation is checked for syntax, concept and type errors.
Each instantiation of a template is compiled separately, which can cause different instantiations from the same original source code to call different actual functions because of overload resolution.
When comes the time for instantiation of a template, there can be different speciations that match the template arguments. There is an ranking of the different (partial) speecializations that the compiler uses for selection. In case of ambiguities, the compiler will fail. This ranking can be influenced by Concepts and SFINAE (Substitution Failure Is Not An Error). If the chosen specialization compiles but behaves differently than expected, this can be a source of programmer confusion.
To Be Continued 2 Oct 2023 Templates add another level of complexity to overload resolution.
In case of a function overload set that includes function templates, overload resolution happens before template specialization. This means, any desired behaviour through explicit function template specialization is not considered during overload resolution, only the primary template is used there.
Class template and variable template specializations can provide specific code for a given set of template arguments. Such specializations must be defined in the namespace of the primary template. To prevent confusion and different compilation of identical looking template instantiations, a specialization should either be defined in the same file as the generic template, or in case of a specialization for a specific template argument type, in the file of the definition of that type.
Functions and lambdas that define parameters with the use of auto
are implicitly templates without using the template
keyword.
Variables defined with the use of auto
keyword get their concrete type deduced from their initializer, as if they were function template parameters.
A constructor template or assignment operator template is never a copy or move operation and hence does not prevent the implicit definition of a copy or move operations even if it looks similar.
Due to the two phase compilation model of templates, name lookup can be surprising in class templates with dependent base classes. A name used in the derived class that is defined in the base might be found in an outer namespace instead.
double foo{0};
template <typename T>
struct base {
int foo;
};
template <typename T>
struct d : base<T>{
auto bar() {
return foo; // matches global foo not base<T>::foo [1]
}
};
In the above example line [1], in place of foo
, either this->foo
or the fully qualified name d::foo
would refer to the member of the base class.
When used appropriately, templates are suitable for embedded and safety critical systems;
void *
-based or macro-based genericity;While using template greatly increases type safety, there can be requirements on template arguments that can neither be specified by concepts nor checked by a compiler. For example, sorting elements requires the comparison function to provide a strict weak ordering which is a property of the values of the type to be sorted by which are impossible to check at compile time for all possible value combinations.
C++ provides means to restrict template arguments. One is to use concepts, that can prevent instantiating a template, but allow for substituting it with an alternative. A second means is to use static_assert
in a template’s definition to prevent certain instantiations.
template<typename T>
struct wrapper {
T x;static_assert(not (std::is_pointer_v<T> || std::is_reference_v<T>));
};template<typename T>
wrapper(T )->wrapper<T>;
int> w{42};
wrapper<// compile error due to static_assert
wrapper x{&w}; int&>{}; // compile error due to static_assert wrapper<
The generic nature of templates require a more elaborate approach to unit tests. Such tests should provide instantiations of the base template and all provided explicit template specializations to ensure that each code path is actually tested. Tests for non-compilability of suppressed instantiations, i.e., through concepts or static_assert, are also beneficial.
Templates allow to reduce the amount of boilerplate code to write, e.g., by providing consistent definitions of operators. However, defining operator function templates in namespace scope can greatly influence compile times due to potential participation in the overload set, whenever the operator is used in code. In addition such generic operator functions might be picked up in inappropriate places causing programmer confusion. Implementing them as hidden friends in a CRTP base class instead makes using operator function templates feasible (see 6.20 Identifier Name Reuse [YOW]).
template <typename T>
struct Plus {
friend constexpr auto operator+(T l, T const &r) {
return l += r;
}
};
struct Int: Plus<Int> {
constexpr auto operator+=(Int const &r) {
val += r.val;
return *this;
}
constexpr Int(int v):val{v}{}
int val;
};
struct Short: Plus<Short> {
constexpr auto operator+=(Short const &r) {
val += r.val;
return *this;
}
constexpr Short(short v):val{v}{}
short val;
};
auto x = Int{4} + Int{38};
auto y = Short{4} + Short{2};
<!--
(*We may wish to summarize)*
-->
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Follow the avoidance mechanisms of ISO/IEC 24772-1 subclause 6.40.5. and the guidance provided in the different related sections of this document.
Use static_assert
to prevent the use of inappropriate template arguments.
Consider using concepts to constrain template parameters.
Prefer concepts over SFINAE.
Be aware that a constructor template or assignment operator function template will not replace compiler-provided special member functions.
For generic operator functions, consider providing them as hidden friends through mix-in class templates.
Use qualified-id or this->
to refer to names that may be found in a dependent base class.
For template specialization, ensure that specializations are declared as follows:
In the same file as the primary template; or
In the same file as the user-defined type for which the specialization is declared.
Prohibit specializing function templates.