当前位置: 首页 > news >正文

东阳实惠营销型网站建设做网站UI工具

东阳实惠营销型网站建设,做网站UI工具,自己做网站的意义,采招网是怎样做的网站第 5 章 基础技巧 5.1 typename 关键字 关键字typename在C标准化过程中被引入进来#xff0c;用来澄清模板内部的一个标识符代表的 是某种类型#xff0c;而不是数据成员。考虑下面这个例子#xff1a; templatetypename T class MyClass { public:void foo() {t… 第 5 章 基础技巧 5.1 typename 关键字 关键字typename在C标准化过程中被引入进来用来澄清模板内部的一个标识符代表的 是某种类型而不是数据成员。考虑下面这个例子 templatetypename T class MyClass { public:void foo() {typename T::SubType* ptr; } }; 其中第二个 typename 被用来澄清 SubType 是定义在 class T 中的一个类型。因此在这里 ptr 是一个指向 T::SubType 类型的指针。 如果没有 typename 的话SubType 会被假设成一个非类型成员比如 static 成员或者一个枚举常量亦或者是内部嵌套类或者 using 声明的 public 别名。这样的话表达式 T::SubType* ptr 会被理解成 class T 的 static 成员 SubType 与 ptr 的乘法运算这不是一个错误因为对 MyClass的某些实例化版本而言这可能是有效的代码。 通常而言当一个依赖于模板参数的名称代表的是某种类型的时候就必须使用 typename。 13.3.2 节会对这一内容做进一步的讨论。 使用 typename 的一种场景是用来声明泛型代码中标准容器的迭代器 // print elements of an STL container templatetypename T void printcoll(T const coll) {typename T::const_iterator pos; // iterator to iterate over colltypename T::const_iterator end(coll.end()); // end positionfor (pos coll.begin(); pos ! end; pos) {std::cout *pos ;}std::cout \n; }int main() {std::string test hello;printcoll(test);return 0; } 5.2零初始化 对于基础类型比如intdouble以及指针类型由于它们没有默认构造函数因此它们不 会被默认初始化成一个有意义的值。比如任何未被初始化的局部变量的值都是未定义的 void foo() {int x; // x has undefined valueint* ptr; // ptr points to anywhere (instead of nowhere) } 因此在定义模板时如果想让一个模板类型的变量被初始化成一个默认值那么只是简单的 定义是不够的因为对内置类型它们不会被初始化 templatetypename T void foo() {T x; // x has undefined value if T is built-in type } 正确做法 void foo() {int x{}; // x has undefined valueint* ptr{}; // ptr points to anywhere (instead of nowhere)std::cout x ptr; } 出于这个原因对于内置类型最好显式的调用其默认构造函数来将它们初始化成 0对于 bool 类型初始化为 false对于指针类型初始化成 nullptr。通过下面你的写法就可以 保证即使是内置类型也可以得到适当的初始化 templatetypename T void foo() {T x{}; // x is zero (or false) if T is a built-in type } 这种初始化的方法被称为“值初始化value initialization”它要么调用一个对象已有的 构造函数要么就用零来初始化这个对象。即使它有显式的构造函数也是这样。 对于用花括号初始 化的情况如果没有可用的默认构造函数它还可以使用列表初始化构造函数initializer-list constructor。 从 C11 开始也可以通过如下方式对非静态成员进行默认初始化 templatetypename T class MyClass { private: T x{}; // zero-initialize x unless otherwise specified … }; 模版参数默认值 templatetypename T void foo(T p T{}) { //OK (must use T() before C11) … } 5.3 使用 this- 对于类模板如果它的基类也是依赖于模板参数的那么对它而言即使 x 是继承而来的使 用 this-x 和 x 也不一定是等效的。比如 templatetypename T class Base { public:void bar(); };templatetypename T class Derived : BaseT { public:void foo() {bar(); // calls external bar() or error} }; Derived 中的 bar()永远不会被解析成 Base 中的 bar()。因此这样做要么会遇到错误要么就 是调用了其它地方的 bar()比如可能是定义在其它地方的 global 的 bar()。 13.4.2 节对这一问题有更详细的讨论。目前作为经验法则建议当使用定义于基类中的、依 赖于模板参数的成员时用 this-或者 BaseT::来修饰它。 5.4 使用裸数组或者字符串常量的模板 当向模板传递裸数组或者字符串常量时需要格外注意以下内容 第一如果参数是按引用传递的那么参数类型不会退化decay 。也就是说当传递 ”hello” 作为参数时模板类型会被推断为 char const[6] 。这样当向模板传递长度不同的裸数组或者 字符串常量时就可能遇到问题因为它们对应的模板类型不一样。只有当按值传递参数时 模板类型才会退化 decay 这样字符串常量会被推断为 char const * 。相关内容会在第 7 章进行讨论。 5.5 成员模板         类的成员也可以是模板对嵌套类和成员函数都是这样。这一功能的作用和优点同样可以通 过 Stack 类模板得到展现。通常只有当两个 stack 类型相同的时候才可以相互赋值 stack 的类型相同说明它们的元素类型也相同。即使两个 stack 的元素类型之间可以隐式转换 也不能相互赋值 Stackint intStack1, intStack2; // stacks for ints Stackfloat floatStack; // stack for floats…intStack1 intStack2; // OK: stacks have same type floatStack intStack1; // ERROR: stacks have different types         默认的赋值运算符要求等号两边的对象类型必须相同因此如果两个 stack 之间的元素类型 不同的话这一条件将得不到满足。         但是只要将赋值运算符定义成模板就可以将两个元素类型可以做转换的 stack 相互赋值。 新的 Stack 定义如下 templatetypename T class Stack { private:std::dequeT elems; // elements public:void push(T const); // push elementvoid pop(); // pop elementT const top() const; // return top elementbool empty() const { // return whether the stack is emptyreturn elems.empty();}// assign stack of elements of type T2templatetypename T2Stack operator (StackT2 const); }; 以上代码中有如下两点改动 1. 赋值运算符的参数是一个元素类型为 T2 的 stack 。 2. 新的模板使用 std::deque 作为内部容器。这是为了方便新的赋值运算符的定义。 新的赋值运算符被定义成下面这样 templatetypename T templatetypename T2 StackT StackT::operator (StackT2 const op2) {StackT2 tmp(op2); // create a copy of the assigned stackelems.clear(); // remove existing elementswhile (!tmp.empty()) { // copy all elementselems.push_front(tmp.top());tmp.pop();}return *this; } 下面先来看一下成员模板的定义语法。在模板类型为 T 的模板内部定义了一个模板类型为 T2 的内部模板         在模板函数内部你可能希望简化 op2 中相关元素的访问。但是由于 op2 属于另一种类型 如果用来实例化类模板的参数类型不同那么实例化出来的类的类型也不同因此最好 使用它们的公共接口。这样访问元素的唯一方法就是通过调用 top() 。这就要求 op2 中所有 元素相继出现在栈顶为了不去改动 op2 就需要做一次 op2 的拷贝。由于 top() 返回的是 最后一个被添加进 stack 的元素因此需要选用一个支持在另一端插入元素的容器这就是 为什么选用 std::deque 的原因因为它的 push_front() 方法可以将元素添加到另一端。 为了访问 op2 的私有成员可以将其它所有类型的 stack 模板的实例都定义成友元 当然这样的赋值就不会改变 floatStack 的类型也不会改变它的元素的类型。在赋值之后 floatStack 存储的元素依然是 float 类型 top() 返回的值也依然是 float 类型。 看上去这个赋值运算符模板不会进行类型检查这样就可以在存储任意类型的两个 stack 之 间相互赋值但是事实不是这样。必要的类型检查会在将源 stack 上文中的 op2 或者其备 份 temp 中的元素插入到目标 stack 中的时候进行 elems . push_front ( tmp . top ()); 比如如果将存储 string 的 stack 赋值给存储 int 的 stack 那么在编译这一行代码的时候会遇 到如下错误信息不能将通过 tmp.top() 返回的 string 用作 elems.push_front() 的参数不同编 译器产生的错误信息可能会有所不同但大体上都是这个意思 成员模板的特例化 成员函数模板也可以被全部或者部分地特例化。比如对下面这个例子 // testtemplate.cpp : 此文件包含 main 函数。程序执行将在此处开始并结束。 //#include iostream #include dequeclass BoolString { private:std::string value; public:BoolString(std::string const s): value(s) {}templatetypename T std::stringT get() const { // get value (converted to T)return value;}templateinline bool getbool() const {return value true || value 1 || value on;} };int main() {std::cout std::boolalpha;BoolString s1(hello);std::cout s1.get() \n; //prints hellostd::cout s1.getbool() \n; //prints falseBoolString s2(on);std::cout s2.getbool() \n; //prints truereturn 0; } 其成员函数模板 get()进行全特例化bool getbool() 注意我们不需要也不能够对特例化的版本进行声明只能定义它们。由于这是一个定义于头 文件中的全实例化版本如果有多个编译单 include 了这个头文件为避免重复定义的错误 必须将它定义成 inline 的。          特殊成员函数的模板         如果能够通过特殊成员函数 copy 或者 move 对象那么相应的特殊成员函数 copy 构造函 数以及 move 构造函数也将可以被模板化。和前面定义的赋值运算符类似构造函数也可 以是模板。但是需要注意的是构造函数模板或者赋值运算符模板不会取代预定义的构造函 数和赋值运算符。成员函数模板不会被算作用来 copy 或者 move 对象的特殊成员函数。在 上面的例子中如果在相同类型的 stack 之间相互赋值调用的依然是默认赋值运算符。 这种行为既有好处也有坏处 1某些情况下对于某些调用构造函数模板或者赋值运算符模板可能比预定义的 copy/move 构造函数或者赋值运算符更匹配虽然这些特殊成员函数模板可能原本只打 算用于在不同类型的 stack 之间做初始化。详情请参见 6.2 节。 2想要对 copy/move 构造函数进行模板化并不是一件容易的事情比如该如何限制其存 在的场景。详情请参见 6.4 节。 template 的使用         某些情况下在调用成员模板的时候需要显式地指定其模板参数的类型 。这时候就需要使用 关键字 template 来确保符号 会被理解为模板参数列表的开始而不是一个比较运算符。考 虑下面这个使用了标准库中的 bitset 的例子 #include bitsettemplateunsigned long N void printBitset(std::bitsetN const bs) {std::cout bs.template to_stringchar,std::char_traitschar,std::allocatorchar(); }对于 bitset 类型的 bs 调用了其成员函数模板 to_string() 并且指定了 to_string() 模板的所有 模板参数。如果没有 .template 的话编译器会将 to_string() 后面的 符号理解成小于运算符 而不是模板的参数列表的开始。 这一这种情况只有在点号前面的对象依赖于模板参数的时候 才会发生。在我们的例子中 bs 依赖于模板参数 N 。 .template 标识符标识符 -template 和 ::template 也类似只能被用于模板内部并且它前 面的对象应该依赖于模板参数。详情请参见 13.3.3 节 泛型 lambdas 和成员模板 在 C14 中引入的泛型 lambdas是一种成员模板的简化。对于一个简单的计算两个任意类 型参数之和的 lambda [] (auto x, auto y) {return x y; } 编译器会默认为它构造下面这样一个类 class SomeCompilerSpecificName { public:SomeCompilerSpecificName(); // constructor only callable by compilertemplatetypename T1, typename T2auto operator() (T1 x, T2 y) const {return x y;} }; 5.6 变量模板 从 C14 开始变量也可以被某种类型参数化。称为变量模板。 例如可以通过下面的代码定义 pi 但是参数化了其类型         template typename T         constexpr T pi { 3.1415926535897932385 }; 注意和其它几种模板类似这个定义最好不要出现在函数内部或者块作用域内部。 在使用变量模板的时候必须指明它的类型。比如下面的代码在定义 pi 的作用域内使用了 两个不同的变量 std :: cout pi double ’ \n ’ ; std :: cout pi float ’ \n ’ ; 用于数据成员的变量模板 变量模板的一种应用场景是用于定义代表类模板成员的变量模板。比如如果像下面这样定 义一个类模板 templatetypename T class MyClass { public:static constexpr int max 1000; }; 那么就可以为 MyClass 的不同特例化版本定义不同的值         template typename T         int myMax MyClass T :: max ; 应用工程师就可以使用下面这样的代码         auto i myMax std :: string ; 而不是         auto i MyClass std :: string :: max ; 这意味着对于一个标准库的类 namespace std { templatetypename T class numeric_limits {public: …static constexpr bool is_signed false; … }; } 可以定义 template typename T constexpr bool isSigned std :: numeric_limits T :: is_signed ; 这样就可以用 isSigned char 代替 std :: numeric_limits char :: is_signed 类型萃取 Suffix_v 从 C17 开始标准库用变量模板为其用来产生一个值布尔型的类型萃取定义了简化方 式。比如为了能够使用 std :: is_const_v T // since C17 而不是 std :: is_const T :: value //since C11 标准库做了如下定义 namespace std { template typename T constexpr bool is_const_v is_const T :: value ; } 5.7 模板参数模板 如果允许模板参数也是一个类模板的话会有不少好处。在这里依然使用 Stack 类模板作为 例子。 对 5.5 节中的 stack 模板如果不想使用默认的内部容器类型 std::deque 那么就需要两次指 定 stack 元素的类型。也就是说为了指定内部容器的类型必须同时指出容器的类型和元素 的类型 Stack int , std :: vector int vStack ; // integer stack that uses a vector 使用模板参数模板在声明 Stack 类模板的时候就可以只指定容器的类型而不去指定容器中 元素的类型 Stack int , std :: vector vStack ; // integer stack that uses a vector 为此就需要在 Stack 的定义中将第二个模板参数声明为模板参数模板。可能像下面这样 #includedequetemplatetypename T,templatetypename Elem class Cont std::deque class Stack { private:ContT elems; // elements public:void push(T const); // push elementvoid pop(); // pop elementT const top() const; // return top elementbool empty() const { // return whether the stack is emptyreturn elems.empty();} … }; 区别在于第二个模板参数被定义为一个类模板 template typename Elem class Cont 默认类型也从 std::dequeT 变成 std::deque 。这个参数必须是一个类模板它将被第一个模 板参数实例化 Cont T elems ; 用第一个模板参数实例化第二个模板参数的情况是由 Stack 自身的情况决定的。实际上可 以在类模板内部用任意类型实例化一个模板参数模板。         和往常一样声明模板参数时可以使用 class 代替 typename 。在 C11 之前 Cont 只能被某 个类模板的名字取代。 从 C11 开始也可以用别名模板 alias template 取代 Cont 但是直到 C17 在声明模 板参数模板时才可以用 typename 代替 class         这两个变化的目的都一样用 class 代替 typename 不会妨碍我们使用别名模板 alias template 作为和 Cont 对应的模板参数。 由于模板参数模板中的模板参数没有被用到作为惯例可以省略它除非它对文档编写有帮 助 成员函数也要做相应的更改。必须将第二个模板参数指定为模板参数模板。比如对于 push() 成员其实现如下 template typename T , template typename class Cont void Stack T , Cont :: push ( T const elem ) { elems . push_back ( elem ); // append copy of passed elem } 注意虽然模板参数模板是类或者别名类 alias templates 的占位符但是并没有与其对 应的函数模板或者变量模板的占位符 模板参数模板的参数匹配 如果你尝试使用新版本的 Stack 可能会遇到错误说默认的 std::deque 和模板参数模板 Cont 不匹配。这是因为在 C17 之前 templatetypename Elem typename Cont std::deque 中 的模板参数必须和实际参数 std::deque 的模板参数匹配对变参模板有些例外见 12.3.4 节。而且实际参数 std::deque 有两个参数第二个是默认参数 allocator 的默认参数也 要被匹配这样 templatetypename Elem typename Cont std::dequ 就不满足以上要求不 过对 C17 可以。 作为变通可以将类模板定义成下面这样 templatetypename T, templatetypename Elem, typename Alloc std::allocatorElem class Cont std::deque class Stack { private:ContT elems; // elements … }; 其中的 Alloc 同样可以被省略掉。        因此最终的 Stack 模板会像下面这样包含了赋值运算符模板 #include iostream #include deque #include cassert #include memory #include vectortemplatetypename T, templatetypename Elem, typename std::allocatorElem class Cont std::dequeclass Stack {private:ContT elems; // elementspublic:void push(T const); // push elementvoid pop(); // pop elementT const top() const; // return top elementbool empty() const { // return whether the stack is emptyreturn elems.empty();}// assign stack of elements of type T2templatetypename T2, templatetypename Elem2,typename std::allocatorElem2 class Cont2StackT, Cont operator (StackT2, Cont2 const);// to get access to private members of any Stack with elements of type T2 :templatetypename, templatetypename, typenameclassfriend class Stack; };templatetypename T, templatetypename, typename class Cont void StackT, Cont::push(T const elem) {elems.push_back(elem); // append copy of passed elem } templatetypename T, templatetypename, typename class Cont void StackT, Cont::pop() {assert(!elems.empty());elems.pop_back(); // remove last element } templatetypename T, templatetypename, typename class Cont T const StackT, Cont::top() const {assert(!elems.empty());return elems.back(); // return copy of last element } templatetypename T, templatetypename, typename class Cont templatetypename T2, templatetypename, typename class Cont2 StackT, Cont StackT, Cont::operator (StackT2, Cont2 const op2) {elems.clear(); // remove existing elementselems.insert(elems.begin(), // insert at the beginningop2.elems.begin(), // all elements from op2op2.elems.end());return *this; }int main() {Stackint iStack; // stack of intsStackfloat fStack; // stack of floats// manipulate int stackiStack.push(1);iStack.push(2);std::cout iStack.top(): iStack.top() \n;// manipulate float stack:fStack.push(3.3);std::cout fStack.top(): fStack.top() \n;// assign stack of different type and manipulate againfStack iStack;fStack.push(4.4);std::cout fStack.top(): fStack.top() \n;// stack for doubless using a vector as an internal containerStackdouble, std::vector vStack;vStack.push(5.5);vStack.push(6.6);std::cout vStack.top(): vStack.top() \n;vStack fStack;std::cout vStack: ;while (!vStack.empty()) {std::cout vStack.top() ;vStack.pop();}std::cout \n;return 0; }   第 6 章 移动语义和 enable_if 6.1 完美转发Perfect Forwarding  假设希望实现的泛型代码可以将被传递参数的基本特性转发出去 可变对象被转发之后依然可变。Const 对象被转发之后依然是 const 的。 可移动对象可以从中窃取资源的对象被转发之后依然是可移动的。 不使用模板的话为达到这一目的就需要对以上三种情况分别编程。比如为了将调用f()时传递的参数转发给函数 g(): // test111.cpp : 此文件包含 main 函数。程序执行将在此处开始并结束。 //#include iostream #include windows.h using namespace std;#include utility #include iostream class X {}; void g(X) {std::cout g() for variable\n; } void g(X const) {std::cout g() for constant\n; } void g(X) {std::cout g() for movable object\n; } // let f() forward argument val to g(): void f(X val) {g(val); // val is non-const lvalue calls g(X) } void f(X const val) {g(val); // val is const lvalue calls g(X const) }void f(X val) {g(std::move(val)); // val is non-const lvalue needs ::move()tocall g(X) } int main() {X v; // create variableX const c; // create constantf(v); // f() for nonconstant object calls f(X) calls g(X)f(c); // f() for constant object calls f(X const) calls g(X const)f(X()); // f() for temporary calls f(X) calls g(X)f(std::move(v)); // f() for movable variable calls f(X) callsg(X) } 这里定义了三种不同的 f()它们分别将其参数转发给 g()         注意其中针对可移动对象一个右值引用的代码不同于其它两组代码它需要用std::move() 来处理其参数因为参数的移动语义不会被一起传递。虽然第三个 f()中的val 被声明成右值引用但是当其在 f()内部被使用时它依然是一个非常量左值参考附录B其行为也将和第一个 f()中的情况一样。因此如果不使用 std::move()的话在第三个f()中调用的将是g(X) 而不是 g(X)。 这个模板只对前两种情况有效对第三种用于可移动对象的情况无效。基于这一原因C11 引入了特殊的规则对参数进行完美转发perfect forwarding。实现这一目的的惯用方法如下 templatetypename T void f(T val) {g(std::forwardT(val)); } 注意 std::move 没有模板参数并且会无条件地移动其参数而 std::forward会跟据被传递参数的具体情况决定是否“转发”其潜在的移动语义。 不要以为模板参数 T 的 T和具体类型 X 的 X是一样的。虽然语法上看上去类似但是它们适用于不同的规则 具体类型 X 的 X声明了一个右值引用参数。只能被绑定到一个可移动对象上一个prvalue比如临时对象一个 xvalue比如通过 std::move()传递的参数更多细节参见附录 B。它的值总是可变的而且总是可以被“窃取”。模板参数 T 的 T声明了一个转发引用亦称万能引用。可以被绑定到可变、不可变比如 const或者可移动对象上。在函数内部这个参数也可以是可变、不可变或者指向一个可以被窃取内部数据的值。 注意 T 必须是模板参数的名字。只是依赖于模板参数是不可以的。对于模板参数T形如typename T::iterator的声明只是声明了一个右值引用不是一个转发引用。 因此一个可以完美转发其参数的程序会像下面这样 #include utility #include iostream class X {}; void g(X) {std::cout g() for variable\n; } void g(X const) {std::cout g() for constant\n; } void g(X) {std::cout g() for movable object\n; }templatetypename T void f(T val) {g(std::forwardT(val)); }int main() {X v; // create variableX const c; // create constantf(v); // f() for nonconstant object calls f(X) calls g(X)f(c); // f() for constant object calls f(X const) calls g(X const)f(X()); // f() for temporary calls f(X) calls g(X)f(std::move(v)); // f() for movable variable calls f(X) callsg(X) } 6.2 特殊成员函数模板 特殊成员函数也可以是模板比如构造函数但是有时候这可能会带来令人意外的结果。 考虑下面这个例子 #include utility #include string #include iostream class Person {private:std::string name;public:// constructor for passed initial name:explicit Person(std::string const n) : name(n) {std::cout copying string-CONSTR for ’ name ’\n;}explicit Person(std::string n) : name(std::move(n)) {std::cout moving string-CONSTR for ’ name ’\n;}// copy and move constructor:Person(Person const p) : name(p.name) {std::cout COPY-CONSTR Person ’ name ’\n;}Person(Person p) : name(std::move(p.name)) {std::cout MOVE-CONSTR Person ’ name ’\n;} };int main() {std::string s sname;Person p1(s); // init with string object calls copying string - CONSTRPerson p2(tmp); // init with string literal calls movingstring-CONSTRPerson p3(p1); // copy Person calls COPY-CONSTRPerson p4(std::move(p1)); // move Person calls MOVE-CONSTreturn 0; } 例子中 Person 类有一个 string 类型的 name 成员和几个初始化构造函数。为了支持移动语义重载了接受 std::string 作为参数的构造函数 现在将上面两个以 std::string 作为参数的构造函数替换为一个泛型的构造函数它将传入的参数完美转发perfect forward给成员 name  #include utility #include string #include iostream class Person {private:std::string name;public:templatetypename Texplicit Person(T str) : name(std::forwardT(str)) {std::cout template for ’ name ’\n;}// copy and move constructor:Person(Person const p) : name(p.name) {std::cout COPY-CONSTR Person ’ name ’\n;}Person(Person p) : name(std::move(p.name)) {std::cout MOVE-CONSTR Person ’ name ’\n;} };int main() {std::string s sname;Person p1(s); // init with string object calls templatePerson p2(tmp); // init with string literal calls template // Person p3(p1); // build errorPerson p4(std::move(p1)); // move Person calls MOVE-CONSTreturn 0; } 问题出在这里根据 C重载解析规则参见 16.2.5 节对于一个非const 左值的Personp成员模板 template Person(STR n) 通常比预定义的拷贝构造函数更匹配 Person (Person const p) 这里 STR 可以直接被替换成 Person 但是对拷贝构造函数还要做一步const 转换。额外提供一个非 const 的拷贝 6.3 通过 std::enable_if禁用模板  从 C11 开始通过 C标准库提供的辅助模板 std::enable_if可以在某些编译期条件下忽略掉函数模板。 比如如果函数模板 foo的定义如下 #include utility #include string #include iostreamtemplatetypename T typename std::enable_if (sizeof(T) 4) ::type foo() { }int main() {foodouble();// build success//foobool();// build error “std::enable_ifsizeof(T)4,void::type foo(void)”的显式 模板 参数无效return 0; } 这一模板定义会在 sizeof(T) 4 不成立的时候被忽略掉。如果 sizeof 4 成立函数模板会展开成 templatetypename T void foo() { } 也就是说 std::enable_if是一种类型萃取type trait它会根据一个作为其第一个模板参数的编译期表达式决定其行为 如果这个表达式结果为 true它的 type 成员会返回一个类型-- 如果没有第二个模板参数返回类型是 void。 -- 否则返回类型是其第二个参数的类型。如果表达式结果 false则其成员类型是未定义的。根据模板的一个叫做SFINAEsubstitute failure is not an error替换失败不是错误将在 8.4 节进行介绍的规则这会导致包含 std::enable_if表达式的函数模板被忽略掉。 由于从 C14 开始所有的模板萃取type traits都返回一个类型因此可以使用一个与之对应的别名模板 std::enable_if_t这样就可以省略掉 template 和::type 了。如下 templatetypename T std::enable_if_t (sizeof(T) 4) foo() { }如果给 std::enable_if或者 std::enable_if_t传递第二个模板参数 templatetypename T std::enable_if_t (sizeof(T) 4), T foo() {return T(); } 那么在 sizeof(T) 4 时enable_if 会被扩展成其第二个模板参数。因此如果与T 对应的模板参数被推断为 MyType而且其 size 大于 4那么其等效于 MyType foo() 6.4 使用 enable_if 通过使用 enable_if可以解决 6.2 节中关于构造函数模板的问题。 我们要解决的问题是当传递的模板参数的类型不正确的时候比如不是std::string 或者可以转换成 std::string 的类型禁用如下构造函数模板 explicit Person(STR n): name(std::forwardSTR(n)) {std::cout TMPL-CONSTR for ’ name ’\n;} 为了这一目的需要使用另一个标准库的类型萃取std::is_convertiable。在C17中相应的构造函数模板的定义如下 templatetypename STR, typename std::enable_if_tstd::is_convertible_vSTR, std::string Person(STR n); 如果 STR 可以转换成 std::string这个定义会扩展成   templatetypename T,typename voidPerson(STR n); 否则这个函数模板会被忽略。 这里同样可以使用别名模板给限制条件定义一个别名 using EnableIfString std::enable_if_tstd::is_convertible_vT, std::string; 现在完整 Person 类如下  #include utility #include string #include iostream #include type_traitstemplatetypename T using EnableIfString std::enable_if_tstd::is_convertible_vT, std::string; class Person {private:std::string name;public:// generic constructor for passed initial name:templatetypename STR, typename EnableIfStringSTRexplicit Person(STR n): name(std::forwardSTR(n)) {std::cout TMPL-CONSTR for ’ name ’\n;}// copy and move constructor:Person(Person const p) : name(p.name) {std::cout COPY-CONSTR Person ’ name ’\n;}Person(Person p) : name(std::move(p.name)) {std::cout MOVE-CONSTR Person ’ name ’\n;} };int main() {std::string s sname;Person p1(s); // init with string object calls TMPL-CONSTRPerson p2(tmp); // init with string literal calls TMPL-CONSTRPerson p3(p1); // OK calls COPY-CONSTRPerson p4(std::move(p1)); // OK calls MOVE-CONSTreturn 0; } 禁用某些成员函数 注意我们不能通过使用 enable_if来禁用 copy/move 构造函数以及赋值构造函数。这是因为成员函数模板不会被算作特殊成员函数依然会生成默认构造函数而且在需要使用copy 构造函数的地方相应的成员函数模板会被忽略掉。因此即使像下面这样定义类模板 #include utility #include string #include iostream #include type_traitsclass C {public:C() default;templatetypename TC(T const) {std::cout tmpl copy constructor\n;} };int main() {C x;C y{ x }; // still uses the predefined copy constructor (not the membertemplate)return 0; } C y{ x };  并不会调用模板调用默认拷贝构造函数 但是也有一个办法可以定义一个接受 const volatile 的 copy 构造函数并将其标示为delete。这样做就不会再隐式声明一个接受 const 参数的 copy 构造函数。在此基础上可以定义一个构造函数模板对于 nonvolatile 的类型它会优选被选择相较于已删除的copy 构造函数 class C {public:C() default;C(C const volatile) delete;// implement copy constructor template with better match:templatetypename Ttemplatetypename TC(T const) {std::cout tmpl copy constructor\n;} }; 这样即使对常规 copy也会调用模板构造函数 C x; C y{x}; // uses the member template 于是就可以给这个模板构造函数添加 enable_if限制。比如可以禁止对通过int 类型参数实例化出来的 C模板实例进行 copy #include utility #include string #include iostream #include type_traitstemplatetypename T class C {public:C() default;C(C const volatile) delete;// if T is no integral type, provide copy constructor templatewith better match:template typename std::enable_if_t !std::is_integralT::value C(CT const) {std::cout tmpl copy constructor\n;} };int main() {Cdouble x;C y{ x }; // still uses the predefined copy constructor (not the membertemplate)return 0; } 6.5 使用 concept 简化 enable_if表达式 即使使用了模板别名enable_if 的语法依然显得很蠢因为它使用了一个变通方法为了达到目的使用了一个额外的模板参数并且通过“滥用”这个参数对模板的使用做了限制。这样的代码不容易读懂也使模板中剩余的代码不易理解。  原则上我们所需要的只是一个能够对函数施加限制的语言特性当这一限制不被满足的时候函数会被忽略掉。 这个语言特性就是人们期盼已久的 concept可以通过其简单的语法对函数模板施加限制条件。不幸的是虽然已经讨论了很久但是 concept 依然没有被纳入C17 标准。一些编译器目前对 concept 提供了试验性的支持不过其很有可能在 C17 之后的标准中得到支持目前确定将在 C20 中得到支持。通过使用 concept 可以写出下面这样的代码 templatetypename STR requires std::is_convertible_vSTR,std::string Person(STR n) : name(std::forwardSTR(n)) { … } 6.6 总结  在模板中可以通过使用“转发引用”亦称“万能引用”声明方式为模板参数T加和 std::forward将模板调用参完美地数转发出去。将完美转发用于成员函数模板时在 copy 或者 move 对象的时候它们可能比预定义的特殊成员函数更匹配。可以通过使用 std::enable_if并在其条件为 false 的时候禁用模板。通过使用 std::enable_if可以避免一些由于构造函数模板或者赋值构造函数模板比隐式产生的特殊构造函数更加匹配而带来的问题。可 以 通 过 删 除 对 const volatile 类 型 参 数 预 定 义 的 特 殊 成 员函数并结合使用std::enable_if将特殊成员函数模板化。通过 concept 可以使用更直观的语法对函数模板施加限制。
http://www.pierceye.com/news/401170/

相关文章:

  • 制作网站素材网页设计代码步骤
  • 做微信头图的网站网页编程语言有哪几种
  • 医生工作室网站建设男女激烈做羞羞事网站网站韩剧
  • 网站里面网友点评怎么做网站开发需要资质吗
  • gta 买房网站建设中软件下载免费大全网站
  • 中国移动网站专门拍短视频的公司
  • 网站制作网站建设报价南通优化网站怎么收费
  • 网站的连接二维码怎么做wordpress.org账号
  • 优秀的网站有哪些内容wordpress重新安装删除哪个文件
  • 网站建设与发布需要什么手机端开发app
  • 无锡做网站无锡网站设计2345网址导航手机上网导航下载
  • html中文美食网站营销型网站维护费用
  • 电商网站建设课设用什么软件制作网站
  • 杭州手机网站wordpress随机调用页面
  • html5网站编写长网页网站
  • 订餐网站系统建设方案建一个网上商城需要多少钱
  • 手机网站asp付费抽奖网站怎么做
  • 国际网站哪里做vs2010 c 建设网站
  • 企业网站更新什么内容永城做网站
  • wordpress 众筹网站模板html5风格网站特色
  • 服装设计参考网站重庆景点排名
  • 网至普的营销型网站建设扬州网站商城建设价格表
  • 成品网站价格表简答网站内容建设的时候内链重要性
  • 视频链接生成器某网站搜索引擎优化
  • flash网站案例vi设计是设计什么
  • ip查询网站备案查询企业网络营销推广平台
  • 多城市网站建设免费制作小程序的平台
  • 郑州网站建设出名吗?wordpress获取登录密码
  • 网站建设论文的开题报告重庆市工程建设信息
  • 商务网站设计方案门户网站的优点