cpp11 language templates

C艹11 Language Extensions – Templates

Extern templates

A template specialization can be explicitly declared as a way to suppress multiple instantiations. For example:

    #include "MyVector.h"

    extern template class MyVector<int>; // Suppresses implicit instantiation below --
                    // MyVector<int> will be explicitly instantiated elsewhere

    void foo(MyVector<int>& v)
    {
        // use the vector in here
    }

The “elsewhere” might look something like this:

    #include "MyVector.h"

    template class MyVector<int>; // Make MyVector available to clients (e.g., of the shared library

This is basically a way of avoiding significant redundant work by the compiler and linker.

See:

Template aliases

How can we make a template that’s “just like another template” but possibly with a couple of template arguments specified (bound)? Consider:

    template<class T>
    using Vec = std::vector<T,My_alloc<T>>;  // standard vector using my allocator

    Vec<int> fib = { 1, 2, 3, 5, 8, 13 }; // allocates elements using My_alloc

    vector<int,My_alloc<int>> verbose = fib; // verbose and fib are of the same type

The keyword using is used to get a linear notation “name followed by what it refers to.” We tried with the conventional and convoluted typedef solution, but never managed to get a complete and coherent solution until we settled on a less obscure syntax.

Specialization works (you can alias a set of specializations but you cannot specialize an alias) For example:

    template<int>
    struct int_exact_traits {   // idea: int_exact_trait<N>::type is a type with exactly N bits
        typedef int type;
    };

    template<>
    struct int_exact_traits<8> {
        typedef char type;
    };

    template<>
    struct int_exact_traits<16> {
        typedef char[2] type;
    };

    // ...

    template<int N>
    using int_exact = typename int_exact_traits<N>::type;  // define alias for convenient notation

    int_exact<8> a = 7; // int_exact<8> is an int with 8 bits

In addition to being important in connection with templates, type aliases can also be used as a different (and IMO better) syntax for ordinary type aliases:

typedef void (*PFD)(double);      // C style
using PF = void (*)(double);      // using plus C-style type
using P = auto (*)(double)->void; // using plus suffix return type

See also:

Variadic templates

Problems to be solved:

  • How to construct a class with 1, 2, 3, 4, 5, 6, 7, 8, 9, or … initializers?
  • How to avoid constructing an object out of parts and then copying the result?
  • How to construct a tuple?

The last question is the key: Think tuple! If you can make and access general tuples the rest will follow.

Here is an example (from “A brief introduction to Variadic templates” (see references)) implementing a general, type-safe, printf(). It would probably be better to use boost::format, but consider:

    const string pi = "pi";
    const char* m = "The value of %s is about %g (unless you live in %s).\n";
    printf(m,  pi, 3.14159,  "Indiana");

The simplest case of printf() is when there are no arguments except the format string, so we’ll handle that first:

    void printf(const char* s)  
    {
        while (s && *s) {
            if (*s=='%' && *++s!='%')   // make sure that there wasn't meant to be more arguments
                            // %% represents plain % in a format string
                 throw runtime_error("invalid format: missing arguments");
            std::cout << *s++;
        }
    }

That done, we must handle printf() with more arguments:

    template<typename T, typename... Args>      // note the "..."
    void printf(const char* s, T value, Args... args)   // note the "..."
    {
        while (s && *s) {
            if (*s=='%' && *++s!='%') { // a format specifier (ignore which one it is)
                std::cout << value;     // use first non-format argument
                return printf(++s, args...);    // "peel off" first argument
            }
            std::cout << *s++;
        }
        throw std::runtime error("extra arguments provided to printf");
    }

This code simply “peels off” the first non-format argument and then calls itself recursively. When there are no more non-format arguments, it calls the first (simpler) printf() (above). This is rather standard functional programming done at compile time. Note how the overloading of << replaces the use of the (possibly erroneous) “hint” in the format specifier.

The Args... defines what is called a “parameter pack.” That’s basically a sequence of (type/value) pairs from which you can “peel off” arguments starting with the first. When printf() is called with one argument, the first printf(const char*) is chosen. When printf() is called with two or more arguments, the second printf(const char*, T value, Args... args)) is chosen, with the first argument as s, the second as value, and the rest (if any) bundled into the parameter pack args for later use. In the call

    printf(++s, args...); 

the parameter pack args is expanded so that the next argument can now be selected as value. This carries on until args is empty (so that the first printf() is called).

If you are familiar with functional programming, you should find this an unusual notation for a pretty standard technique. If not, here are some small technical examples that might help. First we can declare and use a simple variadic template function (just like printf() above):

    template<class ... Types> 
        void f(Types ... args); // variadic template function
                    // (i.e. a function that can take an arbitrary number of arguments of arbitrary types)
    f();        // OK: args contains no arguments
    f(1);       // OK: args contains one argument: int
    f(2, 1.0);  // OK: args contains two arguments: int and double

We can build a variadic type:

    template<typename Head, typename... Tail>
    class tuple<Head, Tail...>
        : private tuple<Tail...> {  // here is the recursion
                // Basically, a tuple stores its head (first (type/value) pair 
                // and derives from the tuple of its tail (the rest of the (type/value) pairs.
                // Note that the type is encoded in the type, not stored as data
        typedef tuple<Tail...> inherited;
    public:
        tuple() { } // default: the empty tuple

        // Construct tuple from separate arguments:
        tuple(typename add_const_reference<Head>::type v, typename add_const_reference<Tail>::type... vtail)
            : m_head(v), inherited(vtail...) { }

        // Construct tuple from another tuple:
        template<typename... VValues>
        tuple(const tuple<VValues...>& other)
        :    m_head(other.head()), inherited(other.tail()) { }

        template<typename... VValues>
        tuple& operator=(const tuple<VValues...>& other)    // assignment
        {
            m_head = other.head();
            tail() = other.tail();
            return *this;
        }

        typename add_reference<Head>::type head() { return m_head; }
        typename add_reference<const Head>::type head() const { return m_head; }

        inherited& tail() { return *this; }
        const inherited& tail() const { return *this; }
    protected:
        Head m_head;
    }

Given that definition, we can make tuples (and copy and manipulate them):

    tuple<string,vector,double> tt("hello",{1,2,3,4},1.2);
    string h = tt.head();   // "hello"
    tuple<vector<int>,double> t2 = tt.tail();   // {{1,2,3,4},1.2};

It can get a bit tedious to mention all of those types, so often, we deduce them from argument types, e.g. using the standard library make_tuple():

    template<class... Types>
    tuple<Types...> make_tuple(Types&&... t)    // this definition is somewhat simplified (see standard 20.5.2.2)
    {
        return tuple<Types...>(t...);
    }

    string s = "Hello";
    vector<int> v = {1,22,3,4,5};
    auto x = make_tuple(s,v,1.2);

See also:

Local types as template arguments

In C艹98, local and unnamed types could not be used as template arguments. This could be a burden, so C艹11 lifts the restriction:

    void f(vector<X>& v)
    {
        struct Less {
            bool operator()(const X& a, const X& b) { return a.v<b.v; }
        };
        sort(v.begin(), v.end(), Less());   // C艹98: error: Less is local
                            // C艹11: ok
    }

In C艹11, we also have the alternative of using a lambda expression:

    void f(vector<X>& v)
    {
        sort(v.begin(), v.end(), 
              [] (const X& a, const X& b) { return a.v<b.v; }); // C艹11 
    }

It is worth remembering that naming action can be quite useful for documentation and an encouragement to good design. Also, non-local (necessarily named) entities can be reused.

C艹11 also allows values of unnamed types to be used as template arguments:

    template<typename T> void foo(T const& t){}
    enum X { x };
    enum { y };

    int main()
    {
        foo(x);     // C艹98: ok; C艹11: ok
        foo(y);     // C艹98: error; C艹11: ok
        enum Z { z };
        foo(z);     // C艹98: error; C艹11: ok 
    }

See also: