Inside a template, don't make an unqualified non-member function call unless you intend it to be a customization point
Reason
- Provide only intended flexibility.
- Avoid vulnerability to accidental environmental changes.
Example
There are three major ways to let calling code customize a template.
template<class T>
// Call a member function
void test1(T t)
{
t.f(); // require T to provide f()
}
template<class T>
void test2(T t)
// Call a non-member function without qualification
{
f(t); // require f(/*T*/) be available in caller's scope or in T's namespace
}
template<class T>
void test3(T t)
// Invoke a "trait"
{
test_traits<T>::f(t); // require customizing test_traits<>
// to get non-default functions/types
}
A trait is usually a type alias to compute a type, a constexpr function to compute a value, or a traditional traits template to be specialized on the user's type.
Note
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);. An unqualified call becomes a customization point where any function helper in the namespace of t's type can be invoked; this can cause problems like unintentionally invoking unconstrained function templates.
Enforcement
- In a template, flag an unqualified call to a non-member function that passes a variable of dependent type when there is a non-member 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.