第5章 技巧性基础:5.5 成员模板

5.5 Member Templates

5.5 成员模板

 

Class members can also be templates. This is possible for both nested classes and member functions. The application and advantage of this ability can again be demonstrated with the Stack<> class template. Normally you can assign stacks to each other only when they have the same type, which implies that the elements have the same type. However, you can’t assign a stack with elements of any other type, even if there is an implicit type conversion for the element types defined:

类成员也可以是模板。嵌套类和成员函数都可以是模板。我们可以通过一个Stack<>类模板来说明这种(作为模板的)能力的优点和应用方法。通常而言,栈之间只有在类型完全相同时才能互相赋值,这意味着元素的类型也应相同。但是,对于元素类型不同的栈,不能对它们进行相互赋值,即使这两种元素的类型之间存在隐式类型转换:

Stack<int> intStack1, intStack2; // stacks for ints
Stack<float> floatStack; // stack for floats
…
intStack1 = intStack2; // OK: 两个栈具有相同的元素类型
floatStack = intStack1; // ERROR: 两个栈的类型不同

The default assignment operator requires that both sides of the assignment operator have the same type, which is not the case if stacks have different element types.

默认赋值运算符要求其两端具有相同的类型,当元素类型不同时,则不符合默认赋值运算符的这个要求。

 

By defining an assignment operator as a template, however, you can enable the assignment of stacks with elements for which an appropriate type conversion is defined. To do this you have to declare Stack<> as follows:

然而,通过将赋值运算符定义为模板,以及为元素类型定义适当的类型转换,就可以进行相互赋值。为了达到这个目的,你需要这样声明Stack<>:

template<typename T>
class Stack {
private:
    std::deque<T> elems; // elements
public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is empty
        return elems.empty();
    }

    // 使用元素类型为T2的栈进行赋值
    template<typename T2>
    Stack& operator= (Stack<T2> const&);
};

The following two changes have been made:

下面进行了两处改动:

1. We added a declaration of an assignment operator for stacks of elements of another type T2.

1. 我们增加了一个赋值运算符的声明,它可以将元素类型为T2的栈赋值给原来的栈。

2. The stack now uses a std::deque<> as an internal container for the elements. Again, this is a consequence of the implementation of the new assignment operator.

2. 栈现在改用std::deque<>作为元素的内部容器。事实上,这是为了满足新赋值运算符的实现需要(译注:std::vector不能使用头插法,但std::deque支持双端操作,而本栈需要用到头插法)。

 

The implementation of the new assignment operator looks like this:

新赋值运算符的实现大致如下:

template<typename T>
template<typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
    Stack<T2> tmp(op2); //创建op2的副本
    elems.clear(); //删除现有元素

    while (!tmp.empty()) { // 拷贝所有元素
        elems.push_front(tmp.top());
        tmp.pop();
    }

    return *this;
}

First let’s look at the syntax to define a member template. Inside the template with template parameter T, an inner template with template parameter T2 is defined:

让我们先来看定义成员模板的语法,在定义有模板参数T的模板内部,还定义了一个含有模板参数T2的内部模板:

template<typename T>
template<typename T2>

Inside the member function, you may expect simply to access all necessary data for the assigned stack op2. However, this stack has a different type (if you instantiate a class template for two different argument types, you get two different class types), so you are restricted to using the public interface. It follows that the only way to access the elements is by calling top(). However, each element has to become a top element, then. Thus, a copy of op2 must first be made, so that the elements are taken from that copy by calling pop(). Because top() returns the last element pushed onto the stack, we might prefer to use a container that supports the insertion of elements at the other end of the collection. For this reason, we use a std::deque<>, which provides push_front() to put an element on the other side of the collection.

在成员函数的内部,你可能只需要访问op2栈的所有必要的元素。然而赋值栈和原栈具有不同的类型(如果你用两种不同类型来实例化同一个模板,那么你将得到两种不同的栈),所以你就不能使用栈本身所提供的公共接口。于是唯一的办法就是调用top(),这样一来每个元素都必须要成为栈顶元素。因此,我们必须先创建一份op2的副本,然后调用副本的top()获取元素。由于top()返回的是最后一个入栈的元素,因此我们必须使用一个支持在另一端插入元素的容器。基于这个原因,我们选择了std::deque<>,它提供了push_front()方法,可以在集合的另一端插入元素。

To get access to all the members of op2 you can declare that all other stack instances are friends:

为了访问op2所有的成员,你可以声明其他所有栈实例为其友元:

template<typename T>
class Stack {
private:
    std::deque<T> elems; // elements
public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element

    bool empty() const { // return whether the stack is empty
        return elems.empty();
    }

    // assign stack of elements of type T2
    template<typename T2>
    Stack& operator= (Stack<T2> const&);

    // to get access to private members of Stack<T2> for any type T2:
    template<typename> friend class Stack;
};

As you can see, because the name of the template parameter is not used, you can omit it:

如你所见,由于没有使用到模板参数,所以可以省略它:

template<typename> friend class Stack;

Now, the following implementation of the template assignment operator is possible:

于是,下面模板赋值运算符的实现成为可能:

template<typename T>
template<typename T2>
Stack<T>& Stack<T>::operator= (Stack<T2> const& op2)
{
    elems.clear(); // remove existing elements
    elems.insert(elems.begin(),      // insert at the beginning
                 op2.elems.begin(),  // all elements from op2
                 op2.elems.end());

    return *this;
}

Whatever your implementation is, having this member template, you can now assign a stack of ints to a stack of floats:

无论如何实现,有了该成员模板,你现在都可以将一个int型栈赋值给float型栈:

Stack<int> intStack; // stack for ints
Stack<float> floatStack; // stack for floats
…
floatStack = intStack; // OK: 两个栈的类型不同,但整型转换为浮点型

Of course, this assignment does not change the type of the stack and its elements. After the assignment, the elements of the floatStack are still floats and therefore top() still returns a float.

当然,这个赋值操作并没有改变原有栈和元素的类型。在赋值以后floatStack的元素仍然是float型,因此它的top依然返回一个浮点数。

 

It may appear that this function would disable type checking such that you could assign a stack with elements of any type, but this is not the case. The necessary type checking occurs when the element of the (copy of the) source stack is moved to the destination stack:

这个赋值函数好像屏蔽了类型检查,看起来你可以用任意类型的栈对目标栈进行赋值。但实际情况并非如此,类型检查仍然存在。当源栈(的副本)的元素被移动到目标栈时,就要执行必要的类型检查。

elems.push_front(tmp.top());

If, for example, a stack of strings gets assigned to a stack of floats, the compilation of this line results in an error message stating that the string returned by tmp.top() cannot be passed as an argument to elems.push_front() (the message varies depending on the compiler, but this is the gist of what is meant):

例如,如果把一个字符串栈赋值给一个浮点数栈,那么编译该行时会报告一个错误信息,说明tmp.top()返回的字符串不能作为elems.push_front()的实参(这个错误信息可能会因编译器不同而有所不同,但大体意思就是这样):

Stack<std::string> stringStack; // stack of strings
Stack<float> floatStack; // stack of floats
…
floatStack = stringStack; // ERROR: std::string不能转换成float

Again, you could change the implementation to parameterize the internal container type:

同样,在实现中,你可以把内部容器类型实现为一个模板参数:

template<typename T, typename Cont = std::deque<T>>
class Stack {
private:
    Cont elems; // elements
public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element

    bool empty() const { // return whether the stack is empty
        return elems.empty();
    }

    // assign stack of elements of type T2
    template<typename T2, typename Cont2>
    Stack& operator= (Stack<T2, Cont2> const&);

    // to get access to private members of Stack<T2> for any type T2:
    template<typename, typename> friend class Stack;
};

Then the template assignment operator is implemented like this:

此时,模板赋值运算符的实现如下:

template<typename T, typename Cont>
template<typename T2, typename Cont2>
Stack<T, Cont>& Stack<T, Cont>::operator= (Stack<T2, Cont2> const& op2)
{
    elems.clear(); // remove existing elements
    elems.insert(elems.begin(),     // insert at the beginning
                 op2.elems.begin(), // all elements from op2
                 op2.elems.end());
    return *this;
}

Remember, for class templates, only those member functions that are called are instantiated. Thus, if you avoid assigning a stack with elements of a different type, you could even use a vector as an internal container:

再次提醒的是:对于类模板而言,只有那些调用的成员函数才会被实例化。因此,如果在元素类型不同的栈之间没有进行相互赋值,你就可以使用vector来作为内部容器:

Stack<int, std::vector<int>> vStack;
...
vStack.push(42);
vStack.push(7);
std::cout << vStack.top() << '\n';

Because the assignment operator template isn’t necessary, no error message of a missing member function push_front() occurs and the program is fine.

因为自定义的模板赋值运算符并不是必不可少的。所以在不存在push_front()的情况下某些程序并不会出现错误信息,而且也能正确运行。

 

For the complete implementation of the last example, see all the files with a name that starts with stack7 in the subdirectory basics.

关于最后一个例子的完整实现,请查看basics子目录下所有以“stack7”开头的文件。

 

Specialization of Member Function Templates

类成员函数模板的特化

 

Member function templates can also be partially or fully specialized. For example, for the following class:

成员函数模板也可以被完全特化和偏特化。例如,下面的类:

class BoolString {
private:
    std::string value;
public:
    BoolString(std::string const& s) : value(s) {}

    template<typename T = std::string>
    T get() const { // get value (converted to T)
        return value;
    }
};

you can provide a full specialization for the member function template as follows:

你可以为成员函数模板提供一个完全特化版本,如下所示:

// 将BoolString::getValue<>() 完全特化为bool型

template<>
inline bool BoolString::get<bool>() const {
    return value == "true" || value == "1" || value == "on";
}

Note that you don’t need and also can’t declare the specializations; you only define them. Because it is a full specialization and it is in a header file you have to declare it with inline to avoid errors if the definition is included by different translation units.

注意,你不需要也不能声明这个特化版本。你只能定义他们。因为它是一个特化版本并且放在头文件中。所以如果要把这个定义包含在不同的翻译单元中,则必须使用inline来声明以避免错误。

You can use class and the full specialization as follows:

你可以使用类和完全特化,如下:

std::cout << std::boolalpha;
BoolString s1("hello");
std::cout << s1.get() << '\n'; //prints hello
std::cout << s1.get<bool>() << '\n'; //prints false
BoolString s2("on");
std::cout << s2.get<bool>() << '\'; //prints true

 

Special Member Function Templates

特殊成员函数模板

 

Template member functions can be used wherever special member functions allow copying or moving objects. Similar to assignment operators as defined above, they can also be constructors. However, note that template constructors or template assignment operators don’t replace predefined constructors or assignment operators.

只要特殊成员函数允许复制或移动对象,就可以使用模板成员函数。与上面定义的赋值运算符类似,他们也可以是构造函数。但是请注意,这些模板构造函数和模板赋值运算符并不会替换掉预定义的构造函数和赋值运算符。

 

Member templates don’t count as the special member functions that copy or move objects. In this example, for assignments of stacks of the same type, the default assignment operator is still called.

成员模板不被看成是复制或移动对象的特殊成员函数。在本例中,对相同类型的栈进行赋值时,仍然会调用默认的赋值运算符。

 

This effect can be good and bad:

这种效果有利有弊:

• It can happen that a template constructor or assignment operator is a better match than the predefined copy/move constructor or assignment operator, although a template version is provided for initialization of other types only. See Section 6.2 on page 95 for details.

尽管提供的模板版本只用于其他类型的初始化,但是模板构造函数或赋值运算符可能产生一个比预定义版本的复制/移动构造函数或赋值运算符更好的匹配。

• It is not easy to “templify” a copy/move constructor, for example, to be able to constrain its existence. See Section 6.4 on page 102 for details.

将复制/移动构造函数进行“模板化”(如限制他们的存在)并不容易。详细信息请参阅第102页6.4节。

 

5.5.1 The .template Construct

5.5.1 .template构造

 

Sometimes, it is necessary to explicitly qualify template arguments when calling a member template. In that case, you have to use the template keyword to ensure that a < is the beginning of the template argument list. Consider the following example using the standard bitset type:

有时,当调用成员模板时有必要显式限定模板实参。在这种情况下,你必须使用template关键字来确保“<”是一个模板参数列表的开始。考虑以下使用标准库bitset类的例子

template<unsigned long N>
void printBitset(std::bitset<N> const& bs) {
    std::cout << bs.template to_string<char,
                                      std::char_traits<char>,
                                      std::allocator<char>>();
}

For the bitset bs we call the member function template to_string(), while explicitly specifying the string type details. Without that extra use of .template, the compiler does not know that the less-than token (<) that follows is not really less-than but the beginning of a template argument list. Note that this is a problem only if the construct before the period depends on a template parameter. In our example, the parameter bs depends on the template parameter N.

对于bitset类的对象bs,我们调用成员函数模板to_string(),同时显式指定字符串类型的详细信息。如果没有使用.template,编译器将不知道下列事实:bs.template后面的小于号(<)不是数学中小于号,而是模板参数列表的起始符号。注意,只有在编译器判断小于号(<)之前,存在依赖于模板参数的对象,才会出现这种问题。在这个例子中,传入参数bs就是依赖于模板参数N的对象。(译注,由于bs是个依赖型名称编译器并不会查找判断其后的to_string成员是否是一个模板因此必须通过.template来显式指定to_string为模板名称。而如果bs类型明确(即是个具体类型),在第一阶段编译时就会检查to_string成员,因此可以发现to_string是个模板,也就不会出现问题,具体见后面《案例分析》)

 

The .template notation (and similar notations such as ->template and ::template) should be used only inside templates and only if they follow something that depends on a template parameter. See Section 13.3.3 on page 230 for details.

只有当其前面存在依赖于模板参数对象时,我们才需要在模板内部使用.template标记(类似地,还有->template或::template标记)。而且这些标记也只能在模板中才能使用。

 

5.5.2 Generic Lambdas and Member Templates

5.5.2 泛型Lambdas和成员模板

Note that generic lambdas, introduced with C++14, are shortcuts for member templates. A simple lambda computing the “sum” of two arguments of arbitrary types:

请注意,C++14引入的泛型lambda是成员模板的一种快捷方式。一个简单的lambda表达式,用于计算任意类型的两个参数之”和”:

[] (auto x, auto y) {
    return x + y;
}

is a shortcut for a default-constructed object of the following class:

它是由下面的类通过默认构造而来的对象的一种快捷方式:

class SomeCompilerSpecificName {
public:
    SomeCompilerSpecificName(); //构造函数:只有编译器可调用

    template<typename T1, typename T2>
    auto operator() (T1 x, T2 y) const {
        return x + y;
    }
};

See Section 15.10.6 on page 309 for details.

更多细节,请参阅第309页的15.10.6节。

【案例分析】.template构造

template<typename T>
int func(T& x, double pi)
{
    return x.template get<3>(); //如果没有template ,则x.get<3>()可能被理解为
                                //(x.get <3) > ()。即<被解析成小于号,而不是期望
                                //的模板参数列表的起始符号。这是由于x是个依赖
                                //型名称,在编译的第1阶段并不会检查get是否是个模板?
                                //而是被当成普通成员变量来处理。因此,会造成错误解析。
                                //所以需要使用template来告诉编译器get是个模板。
}

 

原文链接: https://www.cnblogs.com/5iedu/p/12735663.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第5章 技巧性基础:5.5 成员模板

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/392605

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午2:53
下一篇 2023年4月3日 下午2:53

相关推荐