T.interfaces: Template interfaces

???

T.40: Use function objects to pass operations to algorithms

Reason

Function objects can carry more information through an interface than a "plain" pointer to function. In general, passing function objects gives better performance than passing pointers to functions.

Example
bool greater(double x, double y) { return x>y; }
sort(v, greater);                                  // pointer to function: potentially slow
sort(v, [](double x, double y) { return x>y; });   // function object
sort(v, greater<>);                                // function object

bool greater_than_7(double x) { return x>7; }
auto x = find_if(v, greater_than_7);               // pointer to function: inflexible
auto y = find_if(v, [](double x) { return x>7; }); // function object: carries the needed data
auto z = find_if(v, Greater_than<double>(7));      // function object: carries the needed data

You can, of course, gneralize those functions using auto or (when and where available) concepts. For example:

auto y1 = find_if(v, [](Ordered x) { return x>7; }); // reruire an ordered type
auto z1 = find_if(v, [](auto x) { return x>7; });    // hope that the type has a >
Note

Lambdas generate function objects.

Note

The performance argument depends on compiler and optimizer technology.

Enforcement
  • Flag pointer to function template arguments.
  • Flag pointers to functions passed as arguments to a template (risk of false positives).

T.41: Require complete sets of operations for a concept

Reason

Ease of comprehension. Improved interoperability. Flexibility for template implementers.

Note

The issue here is whether to require the minimal set of operations for a template argument (e.g., == but not != or + but not +=). The rule supports the view that a concept should reflect a (mathematically) coherent set of operations.

Example, bad
class Minimal {
    // ...
};

bool operator==(const Minimal&,const Minimal&);
bool operator<(const Minimal&,const Minimal&);
Minimal operator+(const Minimal&, const Minimal&);
// no other operators

void f(const Minimal& x, const Minimal& y)
{
    if (!(x==y) { /* ... */ }    // OK
    if (x!=y) { /* ... */ }      //surprise! error

    while (!(x<y)) { /* ... */ }    // OK
    while (x>=y) { /* ... */ }      //surprise! error

    x = x+y;        // OK
    x += y;      // surprise! error
}

This is minimal, but surprising and constraining for users. It could even be less efficient.

Example
class Convenient {
    // ...
};

bool operator==(const Convenient&,const Convenient&);
bool operator<(const Convenient&,const Convenient&);
// ... and the other comparison operators ...
Minimal operator+(const Convenient&, const Convenient&);
// .. and the other arithmetic operators ...

void f(const Convenient& x, const Convenient& y)
{
    if (!(x==y) { /* ... */ }    // OK
    if (x!=y) { /* ... */ }      //OK

    while (!(x<y)) { /* ... */ }    // OK
    while (x>=y) { /* ... */ }      //OK

    x = x+y;     // OK
    x += y;      // OK
}

It can be a nuisance to define all operators, but not hard. Hopefully, C++17 will give you comparison operators by default.

Enforcement
  • Flag classes the support "odd" subsets of a set of operators, e.g., == but not != or + but not -. Yes, std::string is "odd", but it's too late to change that.

T.42: Use template aliases to simplify notation and hide implementation details

Reason

Improved readability. Implementation hiding. Note that template aliases replace many uses of traits to compute a type. They can also be used to wrap a trait.

Example
template<typename T, size_t N>
class matrix {
    // ...
    using Iterator = typename std::vector<T>::iterator;
    // ...
};

This saves the user of Matrix from having to know that its elements are stored in a vector and also saves the user from repeatedly typing typename std::vector<T>::.

Example
template<typename T>
using Value_type = typename container_traits<T>::value_type;

This saves the user of Value_type from having to know the technique used to implement value_types.

Enforcement
  • Flag use of typename as a disambiguator outside using declarations.
  • ???

T.43: Prefer using over typedef for defining aliases

Reason

Improved readability: With using, the new name comes first rather than being embedded somewhere in a declaration. Generality: using can be used for template aliases, whereas typedefs can't easily be templates. Uniformity: using is syntactically similar to auto.

Example
typedef int (*PFI)(int);   // OK, but convoluted

using PFI2 = int (*)(int);   // OK, preferred

template<typename T>
typedef int (*PFT)(T);      // error

template<typename T>
using PFT2 = int (*)(T);   // OK
Enforcement
  • Flag uses of typedef. This will give a lot of "hits" :-(

T.44: Use function templates to deduce class template argument types (where feasible)

Reason

Writing the template argument types explicitly can be tedious and unnecessarily verbose.

Example
tuple<int, string, double> t1 = {1, "Hamlet", 3.14};   // explicit type
auto t2 = make_tuple(1, "Ophelia"s, 3.14);         // better; deduced type

Note the use of the s suffix to ensure that the string is a std::string, rather than a C-style string.

Note

Since you can trivially write a make_T function, so could the compiler. Thus, make_T functions may become redundant in the future.

Exception

Sometimes there isn't a good way of getting the template arguments deduced and sometimes, you want to specify the arguments explicitly:

vector<double> v = { 1, 2, 3, 7.9, 15.99 };
list<Record*> lst;
Enforcement

Flag uses where an explicitly specialized type exactly matches the types of the arguments used.

T.46: Require template arguments to be at least Regular or SemiRegular

Reason

Readability. Preventing surprises and errors. Most uses support that anyway.

Example
class X {
        // ...
public:
    explicit X(int);
    X(const X&);            // copy
    X operator=(const X&);
    X(X&&);                 // move
    X& operator=(X&&);
    ~X();
    // ... no moreconstructors ...
};

X x {1};    // fine
X y = x;      // fine
std::vector<X> v(10); // error: no default constructor
Note

Semiregular requires default constructible.

Enforcement
  • Flag types that are not at least SemiRegular.

T.47: Avoid highly visible unconstrained templates with common names

Reason

An unconstrained template argument is a perfect match for anything so such a template can be preferred over more specific types that require minor conversions. This is particularly annoying/dangerous when ADL is used. Common names make this problem more likely.

Example
namespace Bad {
    struct S { int m; };
    template<typename T1, typename T2>
    bool operator==(T1, T2) { cout << "Bad\n"; return true; }
}

namespace T0 {
    bool operator==(int, Bad::S) { cout << "T0\n"; return true; }  // compate to int

    void test()
    {
        Bad::S bad{ 1 };
        vector<int> v(10);
        bool b = 1==bad;
        bool b2 = v.size()==bad;
   }
}

This prints T0 and Bad.

Now the == in Bad was designed to cause trouble, but would you have spotted the problem in real code? The problem is that v.size() returns an unsigned integer so that a conversion is needed to call the local ==; the == in Bad requires no conversions. Realistic types, such as the standard library iterators can be made to exhibit similar anti-social tendencies.

Enforcement

????

T.48: If your compiler does not support concepts, fake them with enable_if

Reason

???

Example
???
Enforcement

???

T.49: Where possible, avoid type-erasure

Reason

Type erasure incurs an extra level of indirection by hiding type information behind a separate compilation boundary.

Example
???

Exceptions: Type erasure is sometimes appropriate, such as for std::function.

Enforcement

???

T.50: Avoid writing an unconstrained template in the same namespace as a type

Reason

ADL will find the template even when you think it shouldn't.

Example
???
Note

This rule should not be necessary; the committee cannot agree on how to fix ADL, but at least making it not consider unconstrained templates would solve many of the actual problems and remove the need for this rule.

Enforcement

??? unfortunately this will get many false positives; the standard library violates this widely, by putting many unconstrained templates and types into the single namespace std