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

太原企业网站制作公司做了5天游戏推广被抓了

太原企业网站制作公司,做了5天游戏推广被抓了,看网站时的关键词,网站打不开怎么解决模板特性之类模板参数推导 使用类模板参数推导默认以拷贝方式推导推导lambda的类型没有类模板部分参数推导使用类模板参数推导代替快捷函数 推导指引使用推导指引强制类型退化非模板推导指引推导指引VS构造函数显式推导指引聚合体的推导指引标准推导指引pair和tuple的推导指引从… 模板特性之类模板参数推导 使用类模板参数推导默认以拷贝方式推导推导lambda的类型没有类模板部分参数推导使用类模板参数推导代替快捷函数 推导指引使用推导指引强制类型退化非模板推导指引推导指引VS构造函数显式推导指引聚合体的推导指引标准推导指引pair和tuple的推导指引从迭代器推导std::array推导(Unordered) Map推导智能指针没有推导指引 在 C17之前你必须明确指出类模板的所有参数。例如你不可以省略下面的 double std::complexdouble c{5.1, 3.3};也不可以省略下面代码中的第二个std::mutex std::mutex mx; std::lock_guardstd::mutex lg(mx);自从C17起必须指明类模板参数的限制被放宽了。通过使用 类模板参数推导(class template argument deduction) (CTAD)只要编译器能根据初始值 推导出 所有模板参数那么就可以不指明参数。 例如 你现在可以这么声明 std::complex c{5.1, 3.3}; // OK推导出std::complexdouble但是在c11会报如下错误 error: missing template arguments before c7 | std::complex c{5.1, 3.3};你现在可以这么写 std::mutex mx; std::lock_guard lg{mx}; // OK推导出std::lock_guardstd::mutex在c11会报如下错误 source:9:21: error: missing template arguments before lg9 | std::lock_guard lg(mx);| 你现在甚至可以让容器来推导元素类型 std::vector v1 {1, 2, 3}; // OK推导出std::vectorint std::vector v2 {hello, world}; // OK推导出std::vectorconst char*上述三个示例的预处理代码如下 std::complexdouble c std::complexdouble{5.0999999999999996, 3.2999999999999998};std::mutex mx std::mutex();std::lock_guardstd::mutex lg std::lock_guardstd::mutex(mx);std::vectorint, std::allocatorint v1 std::vectorint, std::allocatorint {std::initializer_listint{1, 2, 3}, std::allocatorint()};std::vectorconst char *, std::allocatorconst char * v2 std::vectorconst char *, std::allocatorconst char * {std::initializer_listconst char *{hello, world}, std::allocatorconst char *()};使用类模板参数推导 只要能根据初始值推导出所有模板参数就可以使用类模板参数推导。推导过程支持所有方式的初始化只要保证初始化是有效的 std::complex c1{1.1, 2.2}; // 推导出std::complexdouble std::complex c2(2.2, 3.3); // 推导出std::complexdouble std::complex c3 3.3; // 推导出std::complexdouble std::complex c4 {4.4}; // 推导出std::complexdoublec17的预处理代码如下 std::complexdouble c1 std::complexdouble{1.1000000000000001, 2.2000000000000002};std::complexdouble c2 std::complexdouble(2.2000000000000002, 3.2999999999999998);std::complexdouble c3 std::complexdouble(3.2999999999999998, 0.0);std::complexdouble c4 std::complexdouble{4.4000000000000004, 0.0};因为std::complex只需要一个参数就可以初始化并推导出模板参数T namespace std {templatetypename Tclass complex {constexpr complex(const Tre T(), const T im T());...} };所以c3和c4可以正确初始化。对于如下声明 std::complex c1{1.1, 2.2};编译器会查找到构造函数constexpr complex(const T re T(), const T im T());并调用。因为两个参数都是double类型所以编译器会推导出T就是double并生成如下代码 complexdouble::complex(const double re double(), const double im double());注意推导的过程中模板参数必须没有歧义。也就是说如下初始化代码不能通过编译 std::complex c5{5, 3.3}; // ERROR尝试将T推导为int和double像通常的模板一样推导模板参数时不会使用隐式类型转换。也可以对可变参数模板使用类模板参数推导。例如对于一个如下定义的std::tuple namespace std {templatetypename... Typesclass tuple {public:constexpr tuple(const Types...);...}; };如下声明 std::tuple t{42, x, nullptr};将推导出类型std::tupleint, char, std::nullptr_t预处理代码如下 std::tupleint, char, std::nullptr_t t std::tupleint, char, std::nullptr_t{42, x, nullptr};我们也可以推导非类型模板参数。例如我们可以根据传入的参数同时推导数组的元素类型和元素数量 #include iostreamtemplate typename T, int SZ class MyClass {public:MyClass(T ()[SZ]) {} }; int main() { MyClass mc(hello); /* // 推导出T为const charSZ为6*/} 这里我们推导出SZ为6因为传入的字符串字面量有6个字符。预处理代码如下 #include iostreamtemplatetypename T, int SZ class MyClass {public: inline MyClass(T ()[SZ]){} }; /* First instantiated from: insights.cpp:9 */ #ifdef INSIGHTS_USE_TEMPLATE template class MyClassconst char, 6 {public: inline MyClass(const char ()[6]){}}; #endif /* First instantiated from: insights.cpp:9 */ #ifdef INSIGHTS_USE_TEMPLATE template MyClass(const char ()[6]) - MyClassconst char, 6; #endif int main() {MyClassconst char, 6 mc MyClassconst char, 6(hello);return 0; } 你甚至可以推导用作基类的lambda来实现重载或者推导auto模板参数。 默认以拷贝方式推导 类模板参数推导过程中会首先尝试以拷贝的方式初始化。例如首先初始化一个只有一个元素的std::vector std::vector v1{42}; // 一个元素的vectorint预处理代码如下 std::vectorint, std::allocatorint v1 std::vectorint, std::allocatorint {std::initializer_listint{42}, std::allocatorint()};然后使用这个vector初始化另一个vector推导时会解释为创建一个拷贝std::vector v2{v1}; // v2也是一个std::vectorint而不是创建一个只有一个元素的vectorvectorint。 这个规则适用于所有形式的初始化 std::vector v2{v1}; // v2也是vectorint std::vector v3(v1); // v3也是vectorint std::vector v4 {v1}; // v4也是vectorint auto v5 std::vector{v1}; // v5也是vectorint预处理代码如下 std::vectorint, std::allocatorint v2 std::vectorint, std::allocatorint {v1};std::vectorint, std::allocatorint v3 std::vectorint, std::allocatorint (v1);std::vectorint, std::allocatorint v4 std::vectorint, std::allocatorint {v1};std::vectorint, std::allocatorint v5 std::vectorint, std::allocatorint {v1};注意 这是花括号初始化总是把列表中的参数作为元素这一规则的一个例外。如果你传递一个只有一个vector的初值列来初始化另一个vector你将得到一个传入的vector的拷贝。然而如果用多于一个元素的初值列来初始化的话就会把传入的参数作为元素并推导出其类型作为模板参数因为这种情况下无法解释为创建拷贝 std::vector vv{v1, v2}; // vv是一个vectorvectorint预处理代码如下 std::vectorstd::vectorint, std::allocatorint , std::allocatorstd::vectorint, std::allocatorint vv std::vectorstd::vectorint, std::allocatorint , std::allocatorstd::vectorint, std::allocatorint {std::initializer_liststd::vectorint, std::allocatorint {std::vectorint, std::allocatorint (v1), std::vectorint, std::allocatorint (v2)}, std::allocatorstd::vectorint, std::allocatorint ()}; 这引出了一个问题就是对可变参数模板使用类模板参数推导时会发生什么 templatetypename... Args auto make_vector(const Args... elems) {return std::vector{elem...}; } std::vectorint v{1, 2, 3}; auto x1 make_vector(v, v); // vectorvectorint auto x2 make_vector(v); // vectorint还是vectorvectorint?目前不同的编译器会有不同的行为这个问题还在讨论之中。 推导lambda的类型 通过使用类模板参数推导我们可以用lambda的类型确切的说是lambda生成的 闭包类型 作为模板参数来实例化类模板。例如我们可以提供一个泛型类对一个任意回调函数进行包装并统计调用次数 #include utility // for std::forward()templatetypename CB class CountCalls { private:CB callback; // 要调用的回调函数long calls 0; // 调用的次数 public:CountCalls(CB cb) : callback(cb) {}templatetypename... Argsdecltype(auto) operator() (Args... args) {calls;return callback(std::forwardArgs(args)...);}long count() const {return calls;} };这里构造函数获取一个回调函数并进行包装这样在初始化时会把参数的类型推导为CB。例如我们可以使用一个lambda作为参数来初始化一个对象 CountCalls sc{[](auto x, auto y) { return x y; }};这意味着排序准则sc的类型将被推导为CountCallsTypeOfTheLambda。预处理内容为 CountCalls__lambda_26_19 sc CountCalls__lambda_26_19{__lambda_26_19{}};。这样我们可以统计出排序准则被调用的次数 std::vector v{5,4,3,2,1};std::sort(v.begin(), v.end(), // 排序区间std::ref(sc)); // 排序准则std::cout sorted with sc.count() calls\n v.at(0);预处理代码如下 {public: templateclass type_parameter_0_0, class type_parameter_0_1inline /*constexpr */ auto operator()(type_parameter_0_0 x, type_parameter_0_1 y) const{return x y;}#ifdef INSIGHTS_USE_TEMPLATEtemplateinline /*constexpr */ bool operator()int, int(int x, int y) const{return x y;}#endifprivate: templateclass type_parameter_0_0, class type_parameter_0_1static inline /*constexpr */ auto __invoke(type_parameter_0_0 x, type_parameter_0_1 y){return __lambda_26_19{}.operator()type_parameter_0_0, type_parameter_0_1(x, y);}public: // inline /*constexpr */ __lambda_26_19(const __lambda_26_19 ) noexcept default;// /*constexpr */ __lambda_26_19() default;};CountCalls__lambda_26_19 sc CountCalls__lambda_26_19{__lambda_26_19{}};std::vectorint, std::allocatorint v std::vectorint, std::allocatorint {std::initializer_listint{5, 4, 3, 2, 1}, std::allocatorint()};std::sort(v.begin(), v.end(), std::ref(sc));输出 sorted with 4 calls 1这里包装过后的lambda被用作排序准则。注意这里必须要传递引用否则std::sort()将会获取sc的拷贝作为参数计数时只会修改该拷贝内的计数器。然而我们可以直接把包装后的lambda传递给std::for_each()因为该算法非并行版本最后会返回传入的回调函数以便于获取回调函数最终的状态 std::vector v{5,4,3,2,1};auto fo std::for_each(v.begin(), v.end(), CountCalls{[](auto i) {std::cout elem: i \n;}});std::cout output with fo.count() calls\n;预处理代码如下 class __lambda_32_60{public: templateclass type_parameter_0_0inline /*constexpr */ auto operator()(type_parameter_0_0 i) const{(std::operator(std::cout, elem: ) i) \n;}#ifdef INSIGHTS_USE_TEMPLATEtemplateinline /*constexpr */ void operator()int(int i) const{std::operator(std::operator(std::cout, elem: ).operator(i), \n);}#endifprivate: templateclass type_parameter_0_0static inline /*constexpr */ auto __invoke(type_parameter_0_0 i){return __lambda_32_60{}.operator()type_parameter_0_0(i);}public: // inline /*constexpr */ __lambda_32_60(const __lambda_32_60 ) noexcept default;// inline /*constexpr */ __lambda_32_60(__lambda_32_60 ) noexcept default;// /*constexpr */ __lambda_32_60() default;};CountCalls__lambda_32_60 fo std::for_each(v.begin(), v.end(), CountCalls__lambda_32_60{__lambda_32_60{}});输出将会如下排序准则调用次数可能会不同因为sort()的实现可能会不同 elem: 1 elem: 2 elem: 3 elem: 4 elem: 5 output with 5 calls如果计数器是原子的你也可以使用并行算法: std::sort(std::execution::par, v.begin(), v.end(), std::ref(sc));没有类模板部分参数推导 注意不像函数模板类模板不能只指明一部分模板参数然后指望编译器去推导剩余的部分参数。甚至使用指明空模板参数列表也是不允许的。例如 #include algorithm #include iostream #include utility #include vector #include stringusing namespace std;template typename T1, typename T2, typename T3 T2 class C {public:C(T1 x {}, T2 y {}, T3 z {}) {} };// 推导所有参数 C c1(22, 44.3, hi); // OKT1是intT2是doubleT3是const char* C c2(22, 44.3); // OKT1是intT2和3是double C c3(hi, guy); // OKT1、T2、T3都是const char*// 推导部分参数 Cstring c4(hi, my); // ERROR只有T1显式指明 error: too few template arguments for class template C C c5(22, 44.3); // ERRORT1和T2都没有指明 error: too few template arguments for class template C C c6(22, 44.3, 42); // ERRORT1和T2都没有指明 error: too few template arguments for class template C// 指明所有参数 Cstring, string, int c7; // OKT1、T2是stringT3是int Cint, string c8(52, my); // OKT1是intT2、T3是string Cstring, string c9(a, b, c); // OKT1、T2、T3都是string预处理代码如下 // 推导所有参数Cint, double, const char * c1 Cint, double, const char *(22, 44.299999999999997, hi);Cint, double, double c2 Cint, double, double(22, 44.299999999999997, {});Cconst char *, const char *, const char * c3 Cconst char *, const char *, const char *(hi, guy, {});// 指明所有参数Cstd::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar , int c7 Cstd::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar , int(std::basic_stringchar, std::char_traitschar, std::allocatorchar {}, std::basic_stringchar, std::char_traitschar, std::allocatorchar {}, {});Cint, std::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar c8 Cint, std::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar (52, std::basic_stringchar, std::char_traitschar, std::allocatorchar (my, std::allocatorchar()), std::basic_stringchar, std::char_traitschar, std::allocatorchar {});Cstd::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar c9 Cstd::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar , std::basic_stringchar, std::char_traitschar, std::allocatorchar (std::basic_stringchar, std::char_traitschar, std::allocatorchar (a, std::allocatorchar()), std::basic_stringchar, std::char_traitschar, std::allocatorchar (b, std::allocatorchar()), std::basic_stringchar, std::char_traitschar, std::allocatorchar (c, std::allocatorchar())); 注意第三个模板参数有默认值因此只要指明了第二个参数就不需要再指明第三个参数。如果你想知道为什么不支持部分参数推导这里有一个导致这个决定的例子 std::tupleint t(42, 43); // 仍然ERRORstd::tuple是一个可变参数模板因此你可以指明任意数量的模板参数。在这个例子中并不能判断出只指明一个参数是一个错误还是故意的。不幸的是不支持部分参数推导意味着一个常见的编码需求并没有得到解决。我们仍然不能简单的使用一个lambda作为关联容器的排序准则或者无序容器的hash函数 std::setCust coll([] (const Cust x, const Cust y) { // 仍然ERRORreturn x.getName() y.getName();});我们仍然必须指明lambda的类型。例如 auto sortcrit [] (const Cust x, const Cust y) {return x.getName() y.getName();}; std::setCust, decltype(sortcrit) coll(sortcrit); // OK仅仅指明类型是不行的因为容器初始化时会尝试用给出的lambda类型创建一个lambda。但这在C17中是不允许的因为默认构造函数只有编译器才能调用。 使用类模板参数推导代替快捷函数 原则上讲通过使用类模板参数推导我们可以摆脱已有的几个快捷函数模板这些快捷函数的作用其实就是根据传入的参数实例化相应的类模板。一个明显的例子是std::make_pair()它可以帮助我们避免指明传入参数的类型。 例如在如下声明之后 std::vectorint v;我们可以这样 auto p std::make_pair(v.begin(), v.end());而不需要写 std::pairtypename std::vectorint::iterator, typename std::vectorint::iteratorp(v.begin(), v.end());现在这种场景已经不再需要std::make_pair()了我们可以简单的写为std::pair p(v.begin(), v.end());或者std::pair p{v.begin(), v.end()};然而从另一个角度来看std::make_pair()也是一个很好的例子它演示了有时便捷函数的作用不仅仅是推导模板参数。事实上std::make_pair()会使传入的参数退化在C03中以值传递自从C11起使用特征。这样会导致字符串字面量的类型字符数组被推导为const char* auto q std::make_pair(hi, world); // 推导为指针的pair预处理代码如下可以看到q的类型为std::pairconst char*, const char*。 std::pairconst char *, const char * q std::make_pair(hi, world);使用类模板参数推导可能会让事情变得更加复杂。考虑如下这个类似于std::pair的简单的类的声明 templatetypename T1, typename T2 struct Pair1 {T1 first;T2 second;Pair1(const T1 x, const T2 y) : first{x}, second{y} {} };这里元素以引用传入根据语言规则当以引用传递参数时模板参数的类型不会退化。因此当调用 Pair1 p1{hi, world}; // 推导为不同大小的数组的pairT1被推导为char[3]T2被推导为char[6]。原则上讲这样的推导是有效的。然而我们使用了T1和T2来声明成员first和second因此它们被声明为 char first[3]; char second[6];然而使用一个左值数组来初始化另一个数组是不允许的。它类似于尝试编译如下代码 const char x[3] hi; const char y[6] world; char first[3] {x}; // ERROR char second[6] {y}; // ERROR所以代码Pair1 p1{hi, world};报错如下 source:12:45: error: cannot initialize an array element of type char with an lvalue of type const char[3]Pair1(const T1 x, const T2 y) : first{x}, second{y} {^ source:17:11: note: in instantiation of member function Pair1char[3], char[6]::Pair1 requested herePair1 p1{hi, world}; ^ source:12:56: error: cannot initialize an array element of type char with an lvalue of type const char[6]Pair1(const T1 x, const T2 y) : first{x}, second{y} {注意如果我们声明参数时以值传参就不会再有这个问题 tempaltetypename T1, typename T2 struct Pair2 {T1 first;T2 second;Pair2(T1 x, T2 y) : first{x}, second{y} {} };如果我们像下面这样创建新对象 Pair2 p2{hi, world}; // 推导为指针的pairT1和T2都会被推导为const char*。 预处理代码如下 templatetypename T1, typename T2 struct Pair1 {T1 first;T2 second;inline Pair1(T1 x, T2 y): first{x}, second{y}{} };/* First instantiated from: insights.cpp:17 */ #ifdef INSIGHTS_USE_TEMPLATE template struct Pair1const char *, const char * {const char * first;const char * second;inline Pair1(const char * x, const char * y): first{x}, second{y}{}}; #endif/* First instantiated from: insights.cpp:17 */ #ifdef INSIGHTS_USE_TEMPLATE template Pair1(const char * x, const char * y) - Pair1const char *, const char *; #endifint main() {Pair1const char *, const char * p1 Pair1const char *, const char *{hi, world};return 0; }然而因为std::pair的构造函数以引用传参所以下面的初始化正常情况下应该不能通过编译 std::pair p{hi, world}; // 看似会推导出不同大小的数组的pair但是……然而事实上它能通过编译因为std::pair有 推导指引 推导指引 你可以定义特定的 推导指引 来给类模板参数添加新的推导或者修正构造函数定义的推导。 例如你可以定义无论何时推导Pair3的模板参数推导的行为都好像参数是以值传递的 #include iostream #include string #include utilitytemplate typename T1, typename T2 struct Pair3 {T1 first;T2 second;Pair3(const T1 x, const T2 y) : first{x}, second{y} {} }; //为构造函数定义的推到指引 template typename T1, typename T2 Pair3(T1, T2) - Pair3T1, T2;int main() { Pair3 p1{hi, world}; }在-的左侧我们声明了我们 想要推导什么 。这里我们声明的是使用两个以值传递且类型分别为T1和T2的对象创建一个Pair3对象。在-的右侧我们定义了推导的结果。在这个例子中Pair3以类型T1和T2实例化。 预处理代码如下 templatetypename T1, typename T2 struct Pair3 {T1 first;T2 second;inline Pair3(const T1 x, const T2 y): first{x}, second{y}{}};/* First instantiated from: insights.cpp:17 */ #ifdef INSIGHTS_USE_TEMPLATE template struct Pair3const char *, const char * {const char * first;const char * second;inline Pair3(const char *const x, const char *const y): first{x}, second{y}{}}; #endif#ifdef INSIGHTS_USE_TEMPLATE template Pair3(const char (x)[3], const char (y)[6]) - Pair3char[3], char[6]; #endiftemplate typename T1, typename T2 Pair3(T1, T2) - Pair3T1, T2;/* First instantiated from: insights.cpp:17 */ #ifdef INSIGHTS_USE_TEMPLATE template Pair3(const char *, const char *) - Pair3const char *, const char *; #endifint main() {Pair3const char *, const char * p1 Pair3const char *, const char *{hi, world};return 0; }你可能会说这是构造函数已经做到的事情。然而构造函数是以引用传参两者是不同的。一般来说不仅是模板所有以值传递的参数都会 退化 而以引用传递的参数不会退化。 退化 意味着原生数组会转换为指针并且顶层的修饰符例如const或者引用将会被忽略。 如果没有推导指引对于如下声明 Pair3 p3{hi, world};参数x的类型是const char()[3]因此T1被推导为char[3]参数y的类型是const char()[6]因此T2被推导为char[6]。有了推导指引后模板参数就会退化。这意味着传入的数组或者字符串字面量会退化为相应的指针类型。现在如下声明 Pair3 p3{hi, world};推导指引会发挥作用因此会以值传参。因此两个类型都会退化为const char*然后被用作模板参数推导的结果。上面的声明和下述声明是等价 Pair3const char*, const char* p3{hi, world};注意构造函数仍然以引用传参。推导指引只和模板参数的推导相关它与推导出T1和T2之后实际调用的构造函数无关。 使用推导指引强制类型退化 就像上一个例子展示的那样重载推导规则的一个非常重要的用途就是确保模板参数T在推导时发生 退化 。考虑如下的一个经典的类模板 templatetypename T struct C {C(const T) {}... };这里如果我们传递一个字符串字面量hello传递的类型将是const char()[6]因此T被推导为char[6] C x{hello}; // T被推导为char[6]原因是当参数以引用传递时模板参数不会 退化 为相应的指针类型。 通过使用一个简单的推导指引 templatetypename T C(T) - CT;我们就可以修正这个问题 C x{hello}; // T被推导为const char*推导指引以值传递参数因此hello的类型T会退化为const char*。因为这一点任何构造函数里传递引用作为参数的模板类都需要一个相应的推导指引。C标准库中为pair和tuple提供了相应的推导指引。 非模板推导指引 推导指引并不一定是模板也不一定应用于构造函数。例如为下面的结构体添加的推导指引也是有效的 templatetypename T struct S {T val; }; S(const char*) - Sstd::string; // 把S字符串字面量映射为Sstd::string这里我们创建了一个没有相应构造函数的推导指引。推导指引被用来推导参数T然后结构体的模板参数就相当于已经被指明了。 因此下面所有初始化代码都是正确的并且都会把模板参数T推导为std::string S s1{hello}; // OK等同于Sstd::string s1{hello}; S s2 {hello}; // OK等同于Sstd::string s2 {hello}; S s3 S{hello}; // OK两个S都被推导为Sstd::string因为传入的字符串字面量能隐式转换为std::string所以上面的初始化都是有效的。 注意聚合体需要列表初始化。下面的代码中参数推导能正常工作但会因为没有使用花括号导致初始化错误 S s4 hello; // ERROR不能不使用花括号初始化聚合体 S s5(hello); // ERROR不能不使用花括号初始化聚合体推导指引VS构造函数 推导指引会和类的构造函数产生竞争。类模板参数推导时会根据重载情况选择最佳匹配的构造函数/推导指引。如果一个构造函数和一个推导指引匹配优先级相同那么将会优先使用推导指引。 考虑如下定义 templatetypename T struct C1 {C1(const T) {} }; C1(int)-C1long;当传递一个int时将会使用推导指引因为根据重载规则它的匹配度更高。 因此T被推导为long C1 x1{42}; // T被推导为long然而如果我们传递一个char那么构造函数的匹配度更高因为不需要类型转换这意味着T会被推导为char C1 x3{x}; // T被推导为char在重载规则中以值传参和以引用传参的匹配度相同的。然而在相同匹配度的情况下将优先使用推导指引。因此通常会把推导指引定义为以值传参这样做还有类型退化的优点。 显式推导指引 推导指引可以用explicit声明。当出现explicit不允许的初始化或转换时这一条推导指引就会被忽略。例如 templatetypename T struct S {T val; };explicit S(const char*) - Sstd::string;如果用拷贝初始化使用将会忽略这一条推导指引。这意味着下面的初始化是无效的 S s1 {hello}; // ERROR推导指引被忽略因此是无效的报错如下 source:12:29: error: class template argument deduction for ST failed: explicit deduction guide selected in copy-list-initialization12 | int main() { S s1 {hello}; }| ^ source:10:10: note: explicit deduction guide declared here10 | explicit S(const char*) - Sstd::string;| ^直接初始化或者右侧显式推导的方式仍然有效 S s2{hello}; // OK等同于Sstd::string s2{hello}; S s3 S{hello}; // OK S s4 {S{hello}}; // OK预处理代码如下 Sstd::basic_stringchar, std::char_traitschar, std::allocatorchar s2 {std::basic_stringchar, std::char_traitschar, std::allocatorchar (hello, std::allocatorchar())};Sstd::basic_stringchar, std::char_traitschar, std::allocatorchar s3 S{std::basic_stringchar, std::char_traitschar, std::allocatorchar (hello, std::allocatorchar())};Sstd::basic_stringchar, std::char_traitschar, std::allocatorchar s4 {S{std::basic_stringchar, std::char_traitschar, std::allocatorchar (hello, std::allocatorchar())}};另一个例子如下 #include iostream #include string #include utilitytemplate typename T struct Ptr {Ptr(T) { std::cout Ptr(T)\n; }template typename UPtr(U) {std::cout Ptr(U)\n;} };template typename T explicit Ptr(T) - PtrT*;int main() {Ptr p1{42}; // 根据推导指引推导出Ptrint*Ptr p2 42; // 根据构造函数推导出Ptrintint i 42;Ptr p3{i}; // 根据推导指引推导出Ptrint**Ptr p4 i; // 根据构造函数推导出Ptrint* }预处理代码如下 #include iostream #include string #include utilitytemplatetypename T struct Ptr {inline Ptr(T){std::operator(std::cout, Ptr(T)\n);}templatetypename Uinline Ptr(U){std::operator(std::cout, Ptr(U)\n);}};/* First instantiated from: insights.cpp:18 */ #ifdef INSIGHTS_USE_TEMPLATE template struct Ptrint * {inline Ptr(int *){std::operator(std::cout, Ptr(T)\n);}templatetypename Uinline Ptr(U);/* First instantiated from: insights.cpp:18 */#ifdef INSIGHTS_USE_TEMPLATEtemplateinline Ptrint(int){std::operator(std::cout, Ptr(U)\n);}#endif#ifdef INSIGHTS_USE_TEMPLATEtemplateinline Ptrint *(int *);#endif};#endif /* First instantiated from: insights.cpp:19 */ #ifdef INSIGHTS_USE_TEMPLATE template struct Ptrint {inline Ptr(int){std::operator(std::cout, Ptr(T)\n);}templatetypename Uinline Ptr(U);#ifdef INSIGHTS_USE_TEMPLATEtemplateinline Ptrint(int);#endif};#endif /* First instantiated from: insights.cpp:21 */ #ifdef INSIGHTS_USE_TEMPLATE template struct Ptrint ** {inline Ptr(int **);templatetypename Uinline Ptr(U);/* First instantiated from: insights.cpp:21 */#ifdef INSIGHTS_USE_TEMPLATEtemplateinline Ptrint *(int *){std::operator(std::cout, Ptr(U)\n);}#endif};#endif/* First instantiated from: insights.cpp:19 */ #ifdef INSIGHTS_USE_TEMPLATE template Ptr(int) - Ptrint; #endif/* First instantiated from: insights.cpp:22 */ #ifdef INSIGHTS_USE_TEMPLATE template Ptr(int *) - Ptrint *; #endiftemplate typename T explicit Ptr(T) - PtrT*;/* First instantiated from: insights.cpp:18 */ #ifdef INSIGHTS_USE_TEMPLATE template Ptr(int) - Ptrint *; #endif/* First instantiated from: insights.cpp:21 */ #ifdef INSIGHTS_USE_TEMPLATE template Ptr(int *) - Ptrint **; #endifint main() {Ptrint * p1 Ptrint *{42};Ptrint p2 Ptrint(42);int i 42;Ptrint ** p3 Ptrint **{i};Ptrint * p4 Ptrint *(i);return 0; } 聚合体的推导指引 泛型聚合体中也可以通过使用推导指引来支持类模板参数推导。例如对于 templatetypename T struct A {T val; };在没有推导指引的情况下尝试使用类模板参数推导会导致错误 A i1{42}; // ERROR A s1(hi); // ERROR A s2{hi}; // ERROR A s3 hi; // ERROR A s4 {hi}; // ERROR你必须显式指明参数的类型T Aint i2{42}; Astd::string s5 {hi};然而如果有推导指引A(const char*) - Astd::string; A(int) - Aint;的话 你就可以像下面这样初始化聚合体 A i1{42}; //ok A s2{hi}; // OK A s4 {hi}; // OK预处理代码如下 Aint i1 {42};Astd::basic_stringchar, std::char_traitschar, std::allocatorchar s2 {std::basic_stringchar, std::char_traitschar, std::allocatorchar (hi, std::allocatorchar())};Astd::basic_stringchar, std::char_traitschar, std::allocatorchar s4 {std::basic_stringchar, std::char_traitschar, std::allocatorchar (hi, std::allocatorchar())};注意你仍然需要使用花括号像通常的聚合体初始化一样。否则类型T能成功推导出来但初始化会错误 A s1(hi); // ERRORT是string但聚合体不能初始化 A s3 hi; // ERRORT是string但聚合体不能初始化报错如下 source: In function int main(): source:15:14: error: no matching function for call to Astd::__cxx11::basic_stringchar ::A(const char [3])15 | A s1(hi); // ERROR| ^ source:6:8: note: candidate: Astd::__cxx11::basic_stringchar ::A()6 | struct A {| ^ source:6:8: note: candidate expects 0 arguments, 1 provided source:6:8: note: candidate: Astd::__cxx11::basic_stringchar ::A(const Astd::__cxx11::basic_stringchar ) source:6:8: note: no known conversion for argument 1 from const char [3] to const Astd::__cxx11::basic_stringchar source:6:8: note: candidate: Astd::__cxx11::basic_stringchar ::A(Astd::__cxx11::basic_stringchar ) source:6:8: note: no known conversion for argument 1 from const char [3] to Astd::__cxx11::basic_stringchar source:17:12: error: conversion from const char [3] to non-scalar type Astd::__cxx11::basic_stringchar requested17 | A s3 hi; // ERRORstd::array的推导指引是一个有关聚合体推导指引的进一步的例子。 标准推导指引 C17标准在标准库中引入了很多推导指引。 pair和tuple的推导指引 std::pair需要推导指引来确保类模板参数推导时会推导出参数的退化类型 namespace std {templatetypename T1, typename T2struct pair {...constexpr pair(const T1 x, const T2 y); // 以引用传参...};templatetypename T1, typename T2pair(T1, T2) - pairT1, T2; // 以值推导类型 }因此如下声明 std::pair p{hi, wrold}; // 参数类型分别为const char[3]和const char[6]等价于 std::pairconst char*, const char* p{hi, world};可变参数类模板std::tuple也使用了相同的方法 namespace std {templatetypename... Typesclass tuple {public:constexpr tuple(const Types...); // 以引用传参templatetypename... UTypes constexpr tuple(UTypes...);...};templatetypename... Typestuple(Types...) - tupleTypes...; // 以值推导类型 }因此如下声明 std::tuple t{42, hello, nullptr};将会推导出t的类型为std::tupleint, const char*, std::nullptr_t。 从迭代器推导 为了能够从表示范围的两个迭代器推导出元素的类型所有的容器类例如std::vector都有类似于如下的推导指引 // 使std::vector能根据初始的迭代器推导出元素类型 namespace std {templatetypename Iteratorvector(Iterator, Iterator) - vectortypename iterator_traitsIterator::value_type; }下面的例子展示了它的作用 std::setfloat s; std::vector v1(s.begin(), s.end()); // OK推导出std::vectorfloat**注意这里必须使用圆括号来初始化。**如果你使用花括号 std::vector v2{s.begin(), s.end()}; // 注意并不会推导出std::vectorfloat那么这两个参数将会被看作一个初值列的两个元素根据重载规则初值列的优先级更高。因此它等价于 std::vectorstd::setfloat::iterator v2{s.begin(), s.end()};这意味着我们初始化的vector有两个元素第一个元素是一个指向首元素的迭代器第二个元素是指向尾后元素的迭代器。 另一方面考虑 std::vector v3{hi, world}; // OK推导为std::vectorconst char* std::vector v4(hi, world); // OOPS运行时错误v3的声明会初始化一个拥有两个元素的vector两个元素都是字符串字面量v4的初始化会导致运行时错误很可能会导致core dump。 **问题在于字符串字面量被转换成为字符指针也算是有效的迭代器。**因此我们传递了两个 不是 指向同一个对象的迭代器。换句话说我们指定了一个无效的区间。我们推导出了一个std::vectorconst char但是根据这两个字符串字面量在内存中的位置关系我们可能会得到一个bad_alloc异常也可能会因为没有距离而得到一个core dump还有可能得到两个位置之间的未定义范围内的字符。 总而言之使用花括号是最佳的初始化vector的 元素 的方法。唯一的例外是传递单独一个vector这时会优先进行拷贝。当传递别的含义的参数时使用圆括号会更好。 在任何情况下对于像std::vector或其他STL容器一样拥有复杂的构造函数的类模板 强烈建议不要使用类模板参数推导 而是显式指明类型。 std::array推导 有一个更有趣的例子是关于std::array的。为了能够同时推导出元素的类型和数量 std::array a{42, 45, 77}; // OK推导出std::arrayint, 3而定义了下面的推导指引间接的 // 让std::array推导出元素的数量元素的类型必须相同 namespace std {templatetypename T, typename... Uarray(T, U...) - arrayenable_if_t(is_same_vT, U ...), T, (1 sizeof...(U)); }这个推导指引使用了折叠表达式 (is_same_vT, U ...)来确保所有参数的类型相同。因此下面的代码是错误的 std::array a{42, 45, 77.7}; // ERROR元素类型不同注意类模板参数推导的初始化甚至可以在编译期上下文中生效 constexpr std::array arr{0, 8, 15}; // OK推导出std::arrayint, 3(Unordered) Map推导 想让推导指引正常工作是非常困难的。可以通过给关联容器map、multimap、unordered_map、unordered_multimap定义推导指引来展示其复杂程度。 这些容器里元素的类型是std::pairconst keytype, valuetype。这里const是必需的因为元素的位置取决于key的值这意味着如果能修改key的值的话会导致容器内部陷入不一致的状态。 在C17标准中为std::map namespace std {templatetypename Key, typename T, typename Compare lessKey,typename Allocator allocatorpairconst Key, Tclass map {...}; }想出的第一个解决方案是为如下构造函数 map(initializer_listpairconst Key, T, const Compare Compare(),const Allocator Allocator());定义了如下的推导指引 namespace std {templatetypename Key, typename T, typename Compare lessKey,typename Allocator allocatorpairconst Key, Tmap(initializer_listpairconst Key, T, Compare Compare(), Allocator Allocator())- mapKey, T, Compare, Allocator; }所有的参数都以值传递因此这个推导指引允许传递的比较器和分配器像之前讨论的一样发生退化。然而我们在推导指引中直接使用了和构造函数中完全相同的元素类型这意味着初值列的key的类型必须是const的。因此下面的代码不能工作 std::pair elem1{1, 2}; std::pair elem2{3, 4}; ... std::map m1{elem1, elem2}; // 原来的C17推导指引会ERROR这是因为elem1和elem2会被推导为std::pairint, int而推导指引需要pair中的第一个元素是const的类型所以不能成功匹配。因此你仍然要像下面这么写 std::mapint, int m1{elem1, elem2}; // OK因此推导指引中的const必须被删掉 namespace std {templatetypename Key, typename T, typename Compare lessKey,typename Allocator allocatorpairconst Key, Tmap(initializer_listpairKey, T, Compare Compare(), Allocator Allocator())- mapKey, T, Compare, Allocator; }然而为了继续支持比较器和分配器的退化我们还需要为const key类型的pair定义一个重载版本。否则当传递一个const key类型的参数时将会使用构造函数来推导类型这样会导致传递const key和非const key参数时推导的结果会有细微的不同。 智能指针没有推导指引 注意C标准库中某些你觉得应该有推导指引的地方实际上没有推导指引。你可能会希望共享指针和独占指针有推导指引这样你就不用写 std::shared_ptrint sp{new int(7)};而是直接写 std::shared_ptr sp{new int(7)}; // 不支持上边的写法是错误的因为相应的构造函数是一个模板这意味着没有隐式的推导指引 namespace std {templatetypename T class shared_ptr {public:...templatetypename Y explicit shared_ptr(Y* p);...}; }这里Y和T是不同的模板参数这意味着虽然能从构造函数推导出Y但不能推导出T。这是一个为了支持如下写法的特性 std::shared_ptrBase sp{new Derived(...)};假如我们要提供推导指引的话那么相应的推导指引可以简单的写为 namespace std {templatetypename Y shared_ptr(Y*) - shared_ptrY; }然而这可能导致当分配数组时也会应用这个推导指引 std::shared_ptr sp{new int[10]}; // OOPS推导出shared_ptrint就像经常在C遇到的一样我们陷入了一个讨厌的C问题就是一个对象的指针和一个对象的数组拥有或者退化以后拥有相同的类型。 这个问题看起来很危险因此C标准委员会决定不支持这么写。对于单个对象你仍然必须这样调用 std::shared_ptrint sp1{new int}; // OK auto sp2 std::make_sharedint(); // OK对于数组则要 std::shared_ptrstd::string p(new std::string[10],[] (std::string* p) {delete[] p;});或者使用实例化原生数组的智能指针的新特性只需要 std::shared_ptrstd::string[] p{new std::string[10]};
http://www.w-s-a.com/news/380884/

相关文章:

  • 项目网站开发建网站 多少钱
  • wordpress做门户seo培训价格
  • 百度关键词优化软件如何wordpress站点地图优化
  • 使用cnnic证书的网站营销公司有哪些
  • 做电子杂志用什么网站如何将网站生成二维码
  • 三点水网站建设洛阳市建设厅网站
  • 哪家做网站便宜网络推广培训吧
  • 网站怎么做支付非凡软件站
  • 北京谁会做网站开发熊岳网站怎么做
  • 南阳哪有做网站公司定制网站本地企业
  • 平板上做网站的软件邀约网站怎么做请柬
  • 企业网站成品源码邯郸做网站流程
  • 建设网站需要什么技术两个网站放在同一个服务器 备案
  • 焦作做网站推广天津网络关键词排名
  • siteserver cms网站访问量挂机宝可以做网站吗
  • 普宁网站建设公司网络商城设计
  • wordpress主题 外贸网站wordpress安装后输入帐号登不进去
  • 陕西省西安市建设局网站永登网站设计与建设
  • 广东网站设计招工.免费咨询贷款
  • 做试题网站在线做c 题的网站
  • 青岛发现51例阳性南京专业网站优化公司
  • 南昌建站如何上wordpress
  • 洛阳网站建设优惠公司建筑企业上市公司有哪些
  • 营销型网站建设营销型网站建设手机网站设计需要学什么
  • 在线视频网站 一级做爰片南通网站建设找哪家
  • 网站优化文章东莞专业网站建设价钱
  • 哈尔滨网页设计网站模板泰兴建设局网站
  • 响应式网站设计公司报纸做垂直门户网站
  • 陕西旭泽建设有限公司网站企业网站建设软件需求分析
  • 上海公司网站建设方案中企动力西安分公司