第2章 类模板:2.9 类模板实参的推导
Posted 5iedu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第2章 类模板:2.9 类模板实参的推导相关的知识,希望对你有一定的参考价值。
2.9 Class Template Argument Deduction
2.9 类模板实参的推导
Until C++17, you always had to pass all template parameter types to class templates(unless they have default values). Since C++17, the constraint that you always have to specify the template arguments explicitly was relaxed. Instead, you can skip to define the templates arguments explicitly, if the constructor is able to deduce all template parameters (that don’t have a default value),
C++17之前,你总是必须将所有的模板实参传递给类模板(除非它们具有默认值)。从C++17开始,总要显式指定模板实参的限制己经放宽。相反,如果构造函数能够推导出所有模板参数时(这些参数不含默认值),那么就可以忽略模板实参的定义,
For example, in all previous code examples, you can use a copy constructor without specifying the template arguments:
例如,在之前所有的代码例示中,当使用拷贝构造时你可以不指定模板实参:
Stack< int> intStack1; // stack of strings Stack< int> intStack2 = intStack1; // OK :对于所有版本均适合 Stack intStack3 = intStack1; // OK:从 C++17开始
By providing constructors that pass some initial arguments, you can support deduction of the element type of a stack. For example, we could provide a stack that can be initialized by a single element:
通过提供用于传递一些初始参数的构造函数,就可以支持栈元素类型的推导。例如,我们可提供一个由单个元素初始化的栈。
template<typename T> class Stack { private: std::vector<T> elems; // 元素 public: Stack() = default; Stack(T const& elem) //通过一个元素来初始化栈(于是,elems就有了一个元素) : elems({ elem }) { } … };
This allows you to declare a stack as follows:
这使你可以像下面那样声明一个栈:
Stack intStack = 0; // 从C++17起,推导为Stack<int>
By initializing the stack with the integer 0, the template parameter T is deduced to be int, so that a Stack<int> is instantiated.
通过使用整数0来初始化栈,可以推导出模板参数T为int型。这样就实例化了一个Stack<int>。
Note the following:
注意以下几点:
• Due to the definition of the int constructor, you have to request the default constructors to be available with its default behavior, because the default constructor is available only if no other constructor is defined:
由于定义了int型构造函数,你就必须要求默认构造函数的默认行为是可用的。因为在定义其他构造函数的情况下,是不提供默认构造函数的:
Stack() = default;
• The argument elem is passed to elems with braces around to initialize the vector elems with an initializer list with elem as the only argument:
实参elem通过大括号传递给elems,用只有一个elem元素的初始化列表来初始化elems这个vector。
: elems({elem}) //此时,elems只有一个元素(elem)。
There is no constructor for a vector that is able to take a single parameter as initial element directly.
vector并没有能够直接接受单个参数作为初始元素的构造函数。
Note that, unlike for function templates, class template arguments may not be deduced only partially (by explicitly specifying only some of the template arguments). See Section 15.12 on page 314 for details.
注意,与函数模板不同,类模板参数不能仅部分推导(只显式指定一些模板实参)。有关详细信息,请参阅第314页的15.12节。
Class Template Arguments Deduction with String Literals
推导带字符串字面量的类模板实参
In principle, you can even initialize the stack with a string literal:
原则上,你甚至可以使用字符串字面量来初始化这个栈。
Stack stringStack = "bottom"; // 从C++17起,推导为 Stack<char const[7]>
But this causes a lot of trouble: In general, when passing arguments of a template type T by reference, the parameter doesn’t decay, which is the term for the mechanism to convert a raw array type to the corresponding raw pointer type. This means that we really initialize a Stack< char const[7]> and use type char const[7] wherever T is used. For example, we may not push a string of different size, because it has a different type. For a detailed discussion see Section 7.4 on page 115.
但这会带来很多麻烦:通常,当通过引用传递模板类型T的实参,该实参的类型不会退化(decay),这里的术语“退化(decay)”指的是将“原始数组类型转换成相应的原始指针类型”的机制。(译注:字符串字面量是数组类型,当使用引用传递数组时,会推导为“数组引用”类型,不会退化成指针)
However, when passing arguments of a template type T by value, the parameter decays, which is the term for the mechanism to convert a raw array type to the corresponding raw pointer type. That is, the call parameter T of the constructor is deduced to be char const* so that the whole class is deduced to be a Stack<char const*>.
然而,如果是按值传递模板类型T的实参时,该实参类型会退化(术语“退化(decay)”如前所述)。也就是说,推导构造函数中的调用参数T为char const*,从而整个类为Stack< char const*>类型。
For this reason, it might be worthwhile to declare the constructor so that the argument is passed by value:
出于这个原因,通过声明按值传递实参的构造函数是值得的:
template<typename T> class Stack { private: std::vector<T> elems; // elements public: Stack(T elem) // 按值传递一个用于初始化栈的元素 : elems({ elem }) { //为使类模板推导时发生退化(decay) } … };
With this, the following initialization works fine:
这样,以下初始化运作正常:
Stack stringStack = "bottom"; // 从C++17起,推导为Stack<char const*>
In this case, however, we should better move the temporary elem into the stack to avoid unnecessarily copying it:
但是,在这种情况下,我们最好将elem临时对象移动到stack中,以避免不必要的复制:
template<typename T> class Stack { private:std::vector<T> elems; // elements public: Stack(T elem) // initialize stack with one element by value : elems({ std::move(elem) }) { } … };
Deduction Guides
推导向导
Instead of declaring the constructor to be called by value, there is a different solution: Because handling raw pointers in containers is a source of trouble, we should disable automatically deducing raw character pointers for container classes.
除了声明构造函数为按值传递参数外,还有一个不同的解决方案:因为在容器中处理原始指针是问题的根源,所以我们应该禁用容器自动推导原始字符指针的功能。
You can define specific deduction guides to provide additional or fix existing class template argument deductions. For example, you can define that whenever a string literal or C string is passed, the stack is instantiated for std::string:
你可以定义一个特殊的“推导向导“,来提供更多的或修复现有的类模板参数的推导。例如,你可以定义,每当传递的是string字面量或C字符串,就实例化为std::string栈。
Stack( char const*) -> Stack<std::string>;
This guide has to appear in the same scope (namespace) as the class definition.
该向导必须出现在与类定义相同的作用域(命名空间)中。
Usually, it follows the class definition. We call the type following the -> the guided type of the deduction guide.
通常,它跟在类定义后面。我们称跟在“->”之后的类型为“推导向导“的向导类型。
Now, the declaration with
现在,声明为
Stack stringStack{"bottom"}; // OK: Stack<std::string> deduced since C++17
deduces the stack to be a Stack<std::string>.
会将栈推导为Stack<std::string>类型。
However, the following still doesn’t work:
但是,以下仍然无效:
Stack stringStack = "bottom"; //推导为 Stack<std::string>, 但仍无效。
We deduce std::string so that we instantiate a Stack<std::string>:
我们推导std::string,以便实例化一个Stack<std::string>
class Stack { private: std::vector<std::string> elems; // elements public: Stack(std::string const& elem) // initialize stack with one element : elems({ elem }) { } … };
However, by language rules, you can’t copy initialize (initialize using =) an object by passing a string literal to a constructor expecting a std::string. So you have to initialize the stack as follows:
但是,根据语言规则,你无法将字符串字面量通过复制初始化的方式(使用“=“初始化)传递给需要std::string类型的构造函数。因此,你必须按如下方式来初始化栈(译注:直接初始化和复制初始化是有区别的,详见文章后面的《编程实验》):
Stack stringStack{"bottom"}; // 推导为Stack<std::string> ,而且有效。
Note that, if in doubt, class template argument deduction copies. After declaring stringStack as Stack<std::string> the following initializations declare the same type (thus, calling the copy constructor) instead of initializing a stack by elements that are string stacks:
注意,如有疑问,请使用类模板实参推导的副本。在将stringStack声明为Stack<std::string>类型之后,以下的初始化声明的都是相同的类型。因此,通过调用拷贝构造函数,而不是栈元素的方式来初始化stack。
Stack stack2{stringStack}; // 推导为Stack<std::string> Stack stack3(stringStack); // 推导为Stack<std::string> Stack stack4 = {stringStack}; //推导为<std::string>
See Section 15.12 on page 313 for more details about class template argument deduction.
有关类模板实参推导的更多详细信息,请参阅313页的15.12节。
【编程实验】推导向导
#include <iostream> #include <vector> using namespace std; class Test { public: Test(const std::string){} }; template<typename T> class Stack { private:std::vector<T> elems; // elements public: Stack() = default; Stack(const T& elem) // 注意,这里可以按引用传递! : elems({elem}) { } //Stack(const char* elem){} }; Stack(const char*)->Stack<std::string>; //推导向导 int main() { /******直接初始化和复制初始化的区别******/ //1.直接初始化:会先将"abcd"转化为string,再传入Test(string) //2.复制初始化的隐式转换要求:从"abcd"到Test必须是直接生成,中间不能有其他临时对象(如string)。 Test t1("abcd"); //OK //Test t2 = "abcd";//ERROR,复制初始化要求从"abcd"到Test是可直接转换的。即不能先转成string再生成Test。 //要实现直接转换,需要调用Test(const char*),但是由于该类并没提供这样的构造函数, //所以编译失败。 /******推导向导******/ //Stack st1 = "abcd"; //推导为Stack<std::string>类型,但无法编译通过。 //这里由于语言特性,复制初始化(使用“=”初始化)时 //无法将字符串字面量(const char*类型)传递给Stack(std::string)的构造函数。 //如果给Stack<String>加个Stack(const char*)的构造函数,则可编译通过。原理 //见t1对象的构造。 Stack st2("abcd"); //OK,Stack(const char*)->Stack(std::string) Stack st3{ "abcd" };//OK,同上。 //通过副本,调用拷贝构造函数初始化Stack<std::string> Stack st4 = st2; //OK, 调用Stack的拷贝构造函数 Stack st5(st2); //同上 Stack st6{ st2 };//同上 return 0; }
以上是关于第2章 类模板:2.9 类模板实参的推导的主要内容,如果未能解决你的问题,请参考以下文章