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
这基本上是一种避免编译器和链接器进行大量冗余工作的方法。
参见
- 标准 14.7.2 显式实例化
- [N1448==03-0031] Mat Marcus 和 Gabriel Dos Reis:控制隐式模板实例化。
模板别名
我们如何创建一个“就像另一个模板”但可能指定(绑定)了几个模板参数的模板?考虑
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);
另请参见
- 标准 14.6.3 可变参数模板
- [N2151==07-0011] D. Gregor, J. Jarvi:C++0x 标准库的可变参数模板。
- [N2080==06-0150] D. Gregor, J. Jarvi, G. Powell:可变参数模板(修订版 3)。
- [N2087==06-0157] Douglas Gregor:可变参数模板简介。
- [N2772==08-0282] L. Joly, R. Klarer:可变参数函数:可变参数模板或初始化列表? – 修订版 1。
- [N2551==08-0061] Sylvain Pion:C++ 标准库的可变参数
std::min(T, ...)
(修订版 2)。 - Anthony Williams:C++0x 中可变参数模板简介。DevX.com,2009 年 5 月。
局部类型作为模板参数
在 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
}
另请参见
- [N2402=07-0262] Anthony Williams:名称、链接和模板(修订版 2)。
- [N2657] John Spicer:局部和匿名类型作为模板参数。