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 concepts 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 Fruits.

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

???