T.def: Template definitions
???
T.60: Minimize a template's context dependencies
Reason
Eases understanding. Minimizes errors from unexpected dependencies. Eases tool creation.
Example
???
Note
Having a template operate only on its arguments would be one way of reducing the number of dependencies to a minimum, but that would generally be unmanageable. For example, an algorithm usually uses other algorithms.
Enforcement
??? Tricky
T.61: Do not over-parameterize members (SCARY)
Reason
A member that does not depend on a template parameter cannot be used except for a specific template argument. This limits use and typically increases code size.
Example, bad
template<typename T, typename A = std::allocator{}>
// requires Regular<T> && Allocator<A>
class List {
public:
struct Link { // does not depend on A
T elem;
T* pre;
T* suc;
};
using iterator = Link*;
iterator first() const { return head; }
// ...
private:
Node* head;
};
List<int> lst1;
List<int, my_allocator> lst2;
???
This looks innocent enough, but ???
template<typename T>
struct Link {
T elem;
T* pre;
T* suc;
};
template<typename T, typename A = std::allocator{}>
// requires Regular<T> && Allocator<A>
class List2 {
public:
using iterator = Link<T>*;
iterator first() const { return head; }
// ...
private:
Node* head;
};
List<int> lst1;
List<int, my_allocator> lst2;
???
Enforcement
- Flag member types that do not depend on every template argument
- Flag member functions that do not depend on every template argument
T.62: Place non-dependent template members in a non-templated base class
Reason
???
Example
template<typename T>
class Foo {
public:
enum { v1, v2 };
// ...
};
???
struct Foo_base {
enum { v1, v2 };
// ...
};
template<typename T>
class Foo : public Foo_base {
public:
// ...
};
Note
A more general version of this rule would be "If a template class member depends on only N template parameters out of M, place it in a base class with only N parameters." For N == 1, we have a choice of a base class of a class in the surrounding scope as in T.41.
??? What about constants? class statics?
Enforcement
- Flag ???
T.64: Use specialization to provide alternative implementations of class templates
Reason
A template defines a general interface. Specialization offers a powerful mechanism for providing alternative implementations of that interface.
Example
??? string specialization (==)
??? representation specialization ?
Note
???
Enforcement
???
T.65: Use tag dispatch to provide alternative implementations of a function
Reason
A template defines a general interface. ???
Example
??? that's how we get algorithms like `std::copy` which compiles into a `memmove` call if appropriate for the arguments.
Note
When concept
s become available such alternatives can be distinguished directly.
Enforcement
???
T.66: Use selection using enable_if
to optionally define a function
Reason
???
Example
???
Enforcement
???
T.67: Use specialization to provide alternative implementations for irregular types
Reason
???
Example
???
Enforcement
???
T.68: Use {}
rather than ()
within templates to avoid ambiguities
Reason
???
Example
???
Enforcement
???
T.69: Inside a template, don't make an unqualified nonmember function call unless you intend it to be a customization point
Reason
To provide only intended flexibility, and avoid accidental environmental changes.
If you intend to call your own helper function helper(t)
with a value t
that depends on a template type parameter, put it in a ::detail
namespace and qualify the call as detail::helper(t);
. Otherwise the call becomes a customization point where any function helper
in the namespace of t
's type can be invoked instead -- falling into the second option below, and resulting in problems like unintentionally invoking unconstrained function templates of that name that happen to be in the same namespace as t
's type.
There are three major ways to let calling code customize a template.
Call a member function. Callers can provide any type with such a named member function.
template<class T> void test(T t) { t.f(); // require T to provide f() }
Call a nonmember function without qualification. Callers can provide any type for which there is such a function available in the caller's context or in the namespace of the type.
template<class T> void test(T t) { f(t); // require f(/*T*/) be available in caller's scope or in T's namespace }
Invoke a "trait" -- usually a type alias to compute a type, or a
constexpr
function to compute a value, or in rarer cases a traditional traits template to be specialized on the user's type.template<class T> void test(T t) { test_traits<T>::f(t); // require customizing test_traits<> to get non-default functions/types test_traits<T>::value_type x; }
Enforcement
- In a template, flag an unqualified call to a nonmember function that passes a variable of dependent type when there is a nonmember function of the same name in the template's namespace.
T.temp-hier: Template and hierarchy rules:
Templates are the backbone of C++'s support for generic programming and class hierarchies the backbone of its support for object-oriented programming. The two language mechanisms can be used effectively in combination, but a few design pitfalls must be avoided.
T.80: Do not naively templatize a class hierarchy
Reason
Templating a class hierarchy that has many functions, especially many virtual functions, can lead to code bloat.
Example, bad
template<typename T>
struct Container { // an interface
virtual T* get(int i);
virtual T* first();
virtual T* next();
virtual void sort();
};
template<typename T>
class Vector : public Container<T> {
public:
// ...
};
vector<int> vi;
vector<string> vs;
It is probably a dumb idea to define a sort
as a member function of a container, but it is not unheard of and it makes a good example of what not to do.
Given this, the compiler cannot know if vector<int>::sort()
is called, so it must generate code for it.
Similar for vector<string>::sort()
.
Unless those two functions are called that's code bloat.
Imagine what this would do to a class hierarchy with dozens of member functions and dozens of derived classes with many instantiations.
Note
In many cases you can provide a stable interface by not parameterizing a base; see Rule.
Enforcement
- Flag virtual functions that depend on a template argument. ??? False positives
T.81: Do not mix hierarchies and arrays
Reason
An array of derived classes can implicitly "decay" to a pointer to a base class with potential disastrous results.
Example
Assume that Apple
and Pear
are two kinds of Fruit
s.
void maul(Fruit* p)
{
*p = Pear{}; // put a Pear into *p
p[1] = Pear{}; // put a Pear into p[2]
}
Apple aa [] = { an_apple, another_apple }; // aa contains Apples (obviously!)
maul(aa);
Apple& a0 = &aa[0]; // a Pear?
Apple& a1 = &aa[1]; // a Pear?
Probably, aa[0]
will be a Pear
(without the use of a cast!).
If sizeof(Apple) != sizeof(Pear)
the access to aa[1]
will not be aligned to the proper start of an object in the array.
We have a type violation and possibly (probably) a memory corruption.
Never write such code.
Note that maul()
violates the a T*
points to an individual object Rule.
Alternative: Use a proper container:
void maul2(Fruit* p)
{
*p = Pear{}; // put a Pear into *p
}
vector<Apple> va = { an_apple, another_apple }; // aa contains Apples (obviously!)
maul2(aa); // error: cannot convert a vector<Apple> to a Fruit*
maul2(&aa[0]); // you asked for it
Apple& a0 = &aa[0]; // a Pear?
Note that the assignment in maul2()
violated the no-slicing Rule.
Enforcement
- Detect this horror!
T.82: Linearize a hierarchy when virtual functions are undesirable
Reason
???
Example
???
Enforcement
???
T.83: Do not declare a member function template virtual
Reason
C++ does not support that. If it did, vtbls could not be generated until link time. And in general, implementations must deal with dynamic linking.
Example, don't
class Shape {
// ...
template<class T>
virtual bool intersect(T* p); // error: template cannot be virtual
};
Note
We need a rule because people keep asking about this
Alternative
Double dispatch, visitors, calculate which function to call
Enforcement
The compiler handles that.
T.84: Use a non-template core implementation to provide an ABI-stable interface
Reason
Improve stability of code. Avoids code bloat.
Example
It could be a base class:
struct Link_base { // stable
Link* suc;
Link* pre;
};
template<typename T> // templated wrapper to add type safety
struct Link : Link_base {
T val;
};
struct List_base {
Link_base* first; // first element (if any)
int sz; // number of elements
void add_front(Link_base* p);
// ...
};
template<typename T>
class List : List_base {
public:
void put_front(const T& e) { add_front(new Link<T>{e}); } // implicit cast to Link_base
T& front() { static_cast<Link<T>*>(first).val; } // explicit cast back to Link<T>
// ...
};
List<int> li;
List<string> ls;
Now there is only one copy of the operations linking and unlinking elements of a List
.
The Link
and List
classes does nothing but type manipulation.
Instead of using a separate "base" type, another common technique is to specialize for void
or void*
and have the general template for T
be just the safely-encapsulated casts to and from the core void
implementation.
Alternative: Use a PIMPL implementation.
Enforcement
???
T.var: Variadic template rules
???
T.100: Use variadic templates when you need a function that takes a variable number of arguments of a variety of types
Reason
Variadic templates is the most general mechanism for that, and is both efficient and type-safe. Don't use C varargs.
Example
??? printf
Enforcement
* Flag uses of `va_arg` in user code.
T.101: ??? How to pass arguments to a variadic template ???
Reason
???
Example
??? beware of move-only and reference arguments
Enforcement
???
T.102: How to process arguments to a variadic template
Reason
???
Example
??? forwarding, type checking, references
Enforcement
???
T.103: Don't use variadic templates for homogeneous argument lists
Reason
There are more precise ways of specifying a homogeneous sequence, such as an initializer_list
.
Example
???
Enforcement
???