【Json11源码阅读】08 问题解答,Part_6
前面两节我们完成了《c++ primer》16.1的阅读,对模板有了一定的了解,但是仍未能解答我们的疑问。在咨询了学习群的小伙伴后,得到了一个有用的链接,在这里感谢@诸葛不亮
http://en.cppreference.com/w/cpp/language/template_parameters
这里呢,我们就把它翻译一下
模板参数和模板实参
模板包含一个或多个模板参数,通过模板参数列表来进行声明,语法为:
template < parameter-list > declaration
参数列表中的参数可以是以下任何一种类型:
- 非类型模板参数;
- 类型模板参数;
- 模板类型的模板参数
非类型模板参数
有以下4种形式
- type name(optional) (1)
- type name(optional) = default (2)
- type ... name(optional) (3) (since C++11)
- auto name (4) (since C++17)
对应的释义如下:
- (1) 参数名可选的非类型模板参数
- (2) 参数名可选且提供了默认值的非类型模板参数
- (3) 参数名可选的非类型模板参数包(c++11)
- (4) 自动推断类型的非类型模板参数 (注意,也可以是
auto **
或者其它带有auto
关键字类型)(c++17)
非类型模板参数允许的参数类型有:
std::nullptr_t
(C++11);- 整型
- 左值引用(对象或函数的引用);
- 指针(对象或函数的指针);
- 成员的指针(数据成员或函数成员);
- 枚举类型
数组和函数类型也可以出现在模板参数列表中,但是会被自动转换成合适的指针或函数指针。
除左值引用类型外,其它几种类型的模板参数在模板定义内是一个常量值。
形如class Foo
的模板参数并不是一个不具名的非类型模板参数。
类型模板参数
有以下几种形式
- typename name(optional) (1)
- class name(optional) (2)
- typename|class name(optional) = default (3)
- typename|class ... name(optional) (4) (since C++11)
释义如下:
- (1) 参数名可选的类型模板参数
- (2) 和(1)等价
- (3) 参数名可选且提供了默认值的类型模板参数
- (4) 参数名可选的类型模板参数包
参数名代表的是一个具体的类型,在模板实例化时提供。
对于类型模板参数的声明来说,typename
和class
关键字没有区别
模板类型的模板参数
有如下的形式
template < parameter-list > typename(C++17)|class name(optional)
(1)template < parameter-list > typename(C++17)|class name(optional) = default
(2)template < parameter-list > typename(C++17)|class ... name(optional)
(3) (since C++11)
释义如下:
- (1) 参数名可选的模板类型的模板参数
- (2) 参数名可选且提供了默认值的模板类型的模板参数
- (3) 参数名可选的模板类型的模板参数包
模板类型的模板参数声明只能使用class
关键字,在c++17之后没有这个限制。
在模板声明的内部,参数名是一个模板名,且需要实参进行初始化。
template<typename T> class my_array {};
//两个类型模板参数,一个模板类型的模板参数
template<typename K, typename V, template<typename> typename C = my_array>
class Map
{
C<K> key;
C<V> value;
};
模板实参
为了实例化一个模板,每个模板参数(类型、非类型、模板类型)都必须提供相应的实参。
- 对于类模板,实参有三种形式:显式模板实参、默认实参、从初始化器推断(c++2017)
- 对于函数模板,实参也有三种形式:显式模板实参、默认实参、上下文推断
如果模板实参既可以看作是type-id又可以看作是表达式,则始终把它当作type-id来解释,即使相应的模板参数是非类型的。
template<class T> void f(); // #1
template<int I> void f(); // #2
void g() {
f<int()>(); // "int()" is both a type and an expression,
// calls #1 because it is interpreted as a type
}
非类型模板实参
当实例化一个非类型模板参数的模板时,有如下限制:
- 对于整型和其它算法类型, 实参必须是能够转换成相应参数类型的常量表达式
- 对于指向某一对象的指针, 模板实参指向的对象必须具有静态生存期,也可以是等价于空指针的常量表达式或
std::nullptr_t
类型的值 - 对于函数指针, 模板实参必须是指向函数的指针或等价于空指针的常量表达式
- 对于左值引用参数,实参不能引用到一个不具名的临时变量
- 对于指向类的成员的指针,实参必须是一个指向成员的指针,形如
&Class::Member
, 或者等价于空指针的常量表达式,或者std::nullptr_t
类型的值
字符串常量、数组地址、非静态成员的地址不能作为非类型模板参数的实参。
非类型模板参数的实参可以是任何能够转换为相应类型的常量表达式:
template<const int* pci> struct X {};
int ai[10];
X<ai> xi; // ok: array to pointer conversion and cv-qualification conversion
struct Y {};
template<const Y& b> struct Z {};
Y y;
Z<y> z; // ok: no conversion
template<int (&pa)[5]> struct W {};
int b[5];
W<b> w; // ok: no conversion
void f(char);
void f(int);
template<void (*pf)(int)> struct A {};
A<&f> a; // ok: overload resolution selects f(int)
绑定到指针或引用的非类型实参,不能绑定到以下对象:
- a subobject (including non-static class member, base subobject, or array element);
- 临时对象
- 字符串常量
typeid
的返回值__func__
变量
template<class T, const char* p> class X {};
X<int, "Studebaker"> x1; // error: string literal as template-argument
template<int* p> class X {};
int a[10];
struct S
{
int m;
static int s;
} s;
X<&a[2]> x3; // error: address of array element
X<&s.m> x4; // error: address of non-static member
X<&s.s> x5; // ok: address of static member
X<&S::s> x6; // ok: address of static member
template<const int& CRI> struct B {};
B<1> b2; // error: temporary would be required for template argument
int c = 1;
B<c> b1; // ok
类型模板参数的实参
类型模板参数的实参必须是一个type-id
,允许是一个不完整类型:
template<typename T> class X {}; // class template
struct A; // incomplete type
typedef struct {} B; // type alias to an unnamed type
int main()
{
X<A> x1; // ok: 'A' names a type
X<A*> x2; // ok: 'A*' names a type
X<B> x3; // ok: 'B' names a type
}
模板类型的模板实参
模板类型的模板实参必须是一个表示类模板或模板别名的id-expression
如果实参是一个类模板,在匹配时只考虑主模板。如果有部分特例化的情况,只有当这部分特例化的参数被实例化时,才会考虑进行匹配。
template<typename T> class A { int x; }; // primary template
template<class T> class A<T*> { long x; }; // partial specialization
// class template with a template template parameter V
template<template<typename> class V> class C
{
V<int> y; // uses the primary template
V<int*> z; // uses the partial specialization
};
C<A> c; // c.y.x has type int, c.z.x has type long
为了将模板类型的模板实参A与模板参数P进行匹配,A中的每一个参数都要与P中的定义完全一致。
template<typename T> struct eval; // primary template
template<template<typename, typename...> class TT, typename T1, typename... Rest>
struct eval<TT<T1, Rest...>> {}; // partial specialization of eval
template<typename T1> struct A;
template<typename T1, typename T2> struct B;
template<int N> struct C;
template<typename T1, int N> struct D;
template<typename T1, typename T2, int N = 17> struct E;
eval<A<int>> eA; // ok: matches partial specialization of eval
eval<B<int, float>> eB; // ok: matches partial specialization of eval
eval<C<17>> eC; // error: C does not match TT in partial specialization because
// TT's first parameter is a type template parameter,
// while 17 does not name a type
eval<D<int, 17>> eD; // error: D does not match TT in partial specialization
// because TT's second parameter is a type parameter pack,
// while 17 does not name a type
eval<E<int, float>> eE; // error: E does not match TT in partial specialization
// because E's third (default) parameter is a non-type
template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template <class ...Types> class C { /* ... */ };
template<template<class> class P> class X { /* ... */ };
X<A> xa; // OK
X<B> xb; // OK in C++14 after CWG 150
// Error earlier: not an exact match
X<C> xc; // OK in C++14 after CWG 150
// Error earlier: not an exact match
template<template<class ...> class Q> class Y { /* ... */ };
Y<A> ya; // OK
Y<B> yb; // OK
Y<C> yc; // OK
template<auto n> class D { /* ... */ }; // note: C++17
template<template<int> class R> class Z { /* ... */ };
Z<D> zd; // OK
template <int> struct SI { /* ... */ };
template <template <auto> class> void FA(); // note: C++17
FA<SI>(); // Error
默认实参
默认实参由=
来指定。除了参数包之外,其他类型的模板(类型、非类型、模板类型)都可以指定默认实参
在以下情况下不允许指定默认实参:
- 在类外定义的成员模板(默认实参必须在类内的声明处指定)
- 在友元类的声明处
- 函数模板的声明或定义处(c++11后无此限制)
在新标准(c++11)中,在友元函数模板的声明中,只有当这个声明恰巧也是一个定义的时候才允许指定默认实参,并且该函数只有这一份定义。
和函数的默认参数类似,在模板声明和定义中分别指定的默认模板实参会进行自动合并:
template<typename T1, typename T2 = int> class A;
template<typename T1 = int, typename T2> class A;
// the above is the same as the following:
template<typename T1 = int, typename T2 = int> class A;
但是,在同一个作用域中,不能为同一个参数指定两次默认实参
template<typename T = int> class X;
template<typename T = int> class X {}; // error
在模板类型的模板参数列表中,默认实参只影响到参数列表所在的作用域:
// class template, with a type template parameter with a default
template<typename T = float> struct B {};
// template template parameter T has a parameter list, which
// consists of one type template parameter with a default
template<template<typename = float> typename T> struct A
{
void f();
void g();
};
// out-of-body member function template definitions
template<template<typename TT> class T>
void A<T>::f()
{
T<> t; // error: TT has no default in scope
}
template<template<typename TT = char> class T>
void A<T>::g()
{
T<> t; // ok: t is T<char>
}
如果在默认模板参数中使用了成员,则成员访问权限的检查在定义时就进行了,而不是在使用时才检查:
class B {};
template<typename T> class C
{
protected:
typedef T TT;
};
template<typename U, typename V = typename U::TT> class D: public U {};
D<C<B>>* d; // error: C::TT is protected
示例
非类型模板参数
#include <iostream>
// simple non-type template parameter
template<int N>
struct S { int a[N]; };
template<const char*>
struct S2 {};
// complicated non-type example
template
<
char c, // integral type
int (&ra)[5], // lvalue reference to object (of array type)
int (*pf)(int), // pointer to function
int (S<10>::*a)[10] // pointer to member object (of type int[10])
> struct Complicated
{
// calls the function selected at compile time
// and stores the result in the array selected at compile time
void foo(char base)
{
ra[4] = pf(c - base);
}
};
S2<"fail"> s2; // error: string literal cannot be used
char okay[] = "okay"; // static object with linkage
S2< &okay[0] > s2; // error: array element has no linkage
S2<okay> s2; // works
int a[5];
int f(int n) { return n; }
int main()
{
S<10> s; // s.a is an array of 10 int
s.a[9] = 4;
Complicated<'2', a, f, &S<10>::a> c;
c.foo('0');
std::cout << s.a[9] << a[4] << '\n';
}
回到我们的问题
template <class T, class = decltype(&T::to_json)>
Json(const T & t) : Json(t.to_json()) {}
这里声明了两个类型模板参数,
- 一个是具名的类型参数T,
- 一个是省略了参数名且带有默认参数,它的默认值与
T::to_json
的返回值类型有关
为什么定义一个不带名字的模板参数呢?其实这里相当于在给T的实参加限制,第二个参数强调了T
的实参类型必须具有一个to_json
成员函数
Json(const T & t) : Json(t.to_json())
这句的含义是,当我们在调用Json(const T & t)
进行构造时,也会执行Json(t.to_json())
中的逻辑。该类有多个构造函数,具体会执行到哪一个,依赖于t.to_json()
的返回值类型
类似于
Json(const T & t) : base(t)
END
微信公众号:马志峰的编程笔记
记录一名普通程序员的成长之路