parts/6.40.TemplatesAndGenerics-SYM.md

6.40 Templates and Generics [SYM]

6.40.1 Applicability to language

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;

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>;

wrapper<int> w{42};
wrapper x{&w}; // compile error due to static_assert
wrapper<int&>{}; // compile error due to static_assert

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)*
-->

6.40.2 Avoidance mechanisms for language users

To avoid the vulnerability or mitigate its ill effects, C++ software developers can: