cpp11 语言模板

C++11 语言扩展 – 模板

外部模板

模板特化可以显式声明,以抑制多次实例化。例如

    #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
    }

“其他地方”可能看起来像这样

    #include "MyVector.h"

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

这基本上是一种避免编译器和链接器进行大量冗余工作的方法。

参见

模板别名

我们如何创建一个“就像另一个模板”但可能指定(绑定)了几个模板参数的模板?考虑

    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

关键字 using 用于获得一种线性表示法,“名称后面跟着它所指的内容”。我们尝试了传统且复杂的 typedef 解决方案,但直到我们确定了一种不那么晦涩的语法,才设法获得一个完整且连贯的解决方案。

特化有效(您可以为一组特化设置别名,但不能特化别名)例如

    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

除了与模板相关的重要性外,类型别名还可以用作普通类型别名的不同(恕我直言,更好)语法

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

另请参见

  • C++ 草案:14.6.7 模板别名;7.1.3 typedef 说明符
  • [N1489=03-0072] Bjarne Stroustrup 和 Gabriel Dos Reis:C++ 的模板别名
  • [N2258=07-0118] Gabriel Dos Reis 和 Bjarne Stroustrup:模板别名(修订版 3)(最终提案)。

可变参数模板

要解决的问题

  • 如何构造一个具有 1, 2, 3, 4, 5, 6, 7, 8, 9, 或 … 初始化器的类?
  • 如何避免从部件构造一个对象然后复制结果?
  • 如何构造一个 tuple

最后一个问题是关键:思考 tuple!如果你能创建和访问通用元组,其余的就会水到渠成。

这是一个示例(来自“可变参数模板简介”(参见参考文献)),实现了通用的、类型安全的 printf()。使用 boost::format 可能会更好,但请考虑

    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");

printf() 最简单的情况是没有除格式字符串之外的参数,所以我们首先处理它

    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++;
        }
    }

完成之后,我们必须处理带有更多参数的 printf()

    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");
    }

此代码只是“剥离”第一个非格式参数,然后递归调用自身。当没有更多非格式参数时,它调用第一个(更简单)的 printf()(上面)。这是在编译时完成的相当标准的函数式编程。请注意 << 的重载如何取代格式说明符中(可能错误的)“提示”的使用。

Args... 定义了所谓的“参数包”。这基本上是一个(类型/值)对序列,您可以从其中“剥离”参数,从第一个开始。当 printf() 被一个参数调用时,选择第一个 printf(const char*)。当 printf() 被两个或更多参数调用时,选择第二个 printf(const char*, T value, Args... args)),其中第一个参数为 s,第二个为 value,其余的(如果有)捆绑到参数包 args 中以供以后使用。在调用中

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

参数包 args 被展开,以便下一个参数现在可以被选择为值。这会一直持续到 args 为空(以便调用第一个 printf())。

如果您熟悉函数式编程,您会发现这是一种不寻常的表示法,用于一种相当标准的技术。如果不是,这里有一些小的技术示例可能会有所帮助。首先,我们可以声明并使用一个简单的可变参数模板函数(就像上面的 printf() 一样)

    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

我们可以构建一个可变参数类型

    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;
    }

给定该定义,我们可以创建元组(并复制和操作它们)

    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};

提及所有这些类型可能会有点乏味,所以通常我们会从参数类型中推断它们,例如使用标准库 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);

另请参见

局部类型作为模板参数

在 C++98 中,局部和匿名类型不能用作模板参数。这可能是一个负担,所以 C++11 解除了这个限制

    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
    }

在 C++11 中,我们还可以使用 lambda 表达式

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

值得记住的是,命名操作对于文档和鼓励良好设计非常有用。此外,非局部(必然命名)实体可以重用。

C++11 还允许将匿名类型的值用作模板参数

    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 
    }

另请参见