模板右值特化的问题

C++语言 码拜 10年前 (2015-05-11) 1464次浏览 0个评论
#include <iostream>
#include <utility>
#include <string>
#include <typeinfo>

using std::string;

template <typename T>
void showMe(T str){
	std::cout << typeid(T).name() << " str!!!" << std::endl;
}

template <>
void showMe<const string>(const string str){
	std::cout << "const string str" << std::endl;
}

template <>
void showMe<string&&>(string&& str){
	std::cout << "string&& str" << std::endl;
}

int main() {
	const string myStr;
	string myStr1;
	showMe(myStr);
	showMe(myStr1);
	showMe( std::move(string("haha")) );

	return 0;
}

程序输出结果为:
Ss str!!!
Ss str!!!
Ss str!!!

为什么没有调用使用右值形参特化的版本呢?

10分
改成这样就能达到你想要的效果(大概)

#include <iostream>
#include <utility>
#include <string>
#include <typeinfo>

using std::string;

template <typename T>
void showMe(T&& str){
    std::cout << typeid(T).name() << " str!!!" << std::endl;
}

template <>
void showMe<const string&>(const string& str){
    std::cout << "const string str" << std::endl;
}

template <>
void showMe<string>(string&& str){
    std::cout << "string&& str" << std::endl;
}
int main() {
    const string myStr;
    string myStr1;
    showMe(myStr);
    showMe(myStr1);
    showMe(std::move(string("haha")));
    return 0;
}

至于为什么,只能说标准就是这么规定的,不理解只能去读标准
或者考虑一下std::move的签名为什么也是带&&的:

template<class _Ty>
	typename remove_reference<_Ty>::type&&
		move(_Ty&& _Arg)
10分
我来说一些规则吧。首先特化的函数是不参与重载解析的。这就意味着一开始特化的版本就不会被考虑。只会考虑普通的和模板两种。这里没有普通那么久选用模板的。然后实例化模板。实例化之后,这个时候另外一条规则就起作用了,如果实例化之后和特化版本参数一致那么采用特化版本。这些规则在c++最新标准之前都是适用的。至于新标准是否有改这方面就没关注了。
根据楼上两位的帮助,加上MSDN上的一篇文章,我分析了一下:
对于右值实参,传给函数形参时,右值属性会丢失,所以此时T的类型为一普通类型。看下面这个例子。这个例子中template <typename T> void quark(T t) ,我们把模板函数的形参故意写为T而不是T&&。

#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::string;

template <typename T> struct Name;

template <> struct Name<string> {
    static const char * get() {
        return "string";
    }
};

template <> struct Name<const string> {
    static const char * get() {
        return "const string";
    }
};

template <> struct Name<string&> {
    static const char * get() {
        return "string&";
    }
};

template <> struct Name<const string&> {
    static const char * get() {
        return "const string&";
    }
};

template <> struct Name<string&&> {
    static const char * get() {
        return "string&&";
    }
};

template <> struct Name<const string&&> {
    static const char * get() {
        return "const string&&";
    }
};

template <typename T> void quark(T t) {
    cout << "t: " << t << endl;
    cout << "T: " << Name<T>::get() << endl;
    cout << "T&&: " << Name<T&&>::get() << endl;
    cout << endl;
}

string strange() {
    return "strange()";
}

const string charm() {
    return "charm()";
}

int main() {
    string up("up");
    const string down("down");

    quark(up);
    quark(down);
    quark(strange());
    quark(charm());
}

函数的输出结果为:
t: up
T: string
T&&: string&&

t: down
T: string
T&&: string&&

t: strange()
T: string
T&&: string&&

t: charm()
T: string
T&&: string&&

看后两项,发现无论是传入左值string还是右值string&&,推导出的T的类型均为普通string,对应的隐式实例化函数为:
void quark(string t),而不是void quark(string&& t)。加上下面这两条规则:
1. 特化的模板函数不参与重载解析;
2. 如果实例化之后和特化版本参数一致,那么采用特化版本。

对于一楼中的我的原问题,可以得出结论:
当调用showMe(std::move(string(“haha”)));时,虽然函数实参确实为右值,但是隐式实例化之后,得到的实例化版本为:
void showMe(string str),确实与特化版本void showMe<string&&>(string&& str)匹配不上。所以只好调用默认版本,输出Ss str!!!

(在模板世界中,T&& 称为Universal Reference. T&& is really special in a template function and is not a rvalue reference. It is named universal reference)
而将形参改为通用引用 T&&后,对于我的小demo,我们再来看一下输出结果:

template <typename T> void quark(T&& t) {
    cout << "t: " << t << endl;
    cout << "T: " << Name<T>::get() << endl;
    cout << "T&&: " << Name<T&&>::get() << endl;
    cout << endl;
}

t: up
T: string&
T&&: string&

t: down
T: const string&
T&&: const string&

t: strange()
T: string
T&&: string&&

t: charm()
T: const string
T&&: const string&&
这时可以看到,对于左值,T&& t 推导出的T为string&;对于右值,T&& t 推导出的T为string。所以对于传入的右值string&&,模板函数template <typename T> void showMe(T&& str) 隐式实例化的结果为:void showMe<string>(string&& str),而这个实例化的版本正好和先前特化的版本一致,所以调用特化版本。问题搞定!

本帖在原问题和新例子之间来回跳跃,希望不影响阅读。


CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明模板右值特化的问题
喜欢 (0)
[1034331897@qq.com]
分享 (0)

文章评论已关闭!