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<T>>
// requires Regular<T> && Allocator<A>
class List {
public:
struct Link { // does not depend on A
T elem;
Link* pre;
Link* suc;
};
using iterator = Link*;
iterator first() const { return head; }
// ...
private:
Link* head;
};
List<int> lst1;
List<int, My_allocator> lst2;
This looks innocent enough, but now Link formally depends on the allocator (even though it doesn't use the allocator). This forces redundant instantiations that can be surprisingly costly in some real-world scenarios. Typically, the solution is to make what would have been a nested class non-local, with its own minimal set of template parameters.
template<typename T>
struct Link {
T elem;
Link* pre;
Link* suc;
};
template<typename T, typename A = std::allocator<T>>
// requires Regular<T> && Allocator<A>
class List2 {
public:
using iterator = Link<T>*;
iterator first() const { return head; }
// ...
private:
Link<T>* head;
};
List2<int> lst1;
List2<int, My_allocator> lst2;
Some people found the idea that the Link no longer was hidden inside the list scary, so we named the technique SCARY. From that academic paper: "The acronym SCARY describes assignments and initializations that are Seemingly erroneous (appearing Constrained by conflicting generic parameters), but Actually work with the Right implementation (unconstrained bY the conflict due to minimized dependencies)."
Note
This also applies to lambdas that don't depend on all of the template parameters.
Enforcement
- Flag member types that do not depend on every template parameter
- Flag member functions that do not depend on every template parameter
- Flag lambdas or variable templates that do not depend on every template parameter