C++函数模版特化的问题

263 阅读4分钟

[TOC]

time: 2017/08/16

问题

在cpp2lua的代码中,源代码有用到函数模版的特化,结果在特化参数为字符串指针(*const char**)的时候得到的是userdata类型,并不是我们想要的字符串类型。抛开lua来说,就是调用了并不是想要的那个函数,这个问题就是模版特化的问题。

部分代码如下:

// 定义1: 基础模版函数
template<typename T>
inline void PushToLua(lua_State *L, T data) {
  CppToLua<T>::ConvertUserdata(L, data);
};
// 定义2:基础模版函数(类型指针)
template<typename T>
inline void PushToLua(lua_State *L, T* data) {
  CppToLua<T>::ConvertUserdata(L, data);
};
// 定义3:特化的模版函数
template<>
inline void PushToLua(lua_State *L, const char* data) {
  lua_pushstring(L, data);
}
// lua代码使用
PushToLua<R>(L, result);

结果:

// PushToLua传入的为字符串
print(test:GetStr())
// 打印结果,我想要的是GetStr返回的字符串,比如:"test"
userdata: 00921DD8

调用了基础模版函数,而不是我想要的特化的函数

// 想要调用的函数:定义3
inline void PushToLua(lua_State *L, const char* data)
// 实际上调用的函数:定义1
inline void PushToLua(lua_State *L, T data)

解惑

基础知识

先了解一些基础知识

  • 函数模版的全特化有两种写法[2]
template < >
int compare<const char*>(const char* left, const char* right)
{
    std::cout <<"in special template< >..." <<std::endl;

    return strcmp(left, right);
}

也可以

template < >
int compare(const char* left, const char* right)
{
    std::cout <<"in special template< >..." <<std::endl;

    return strcmp(left, right);
}
  • 重载和特化

(1)函数模版只有全特化,没有偏特化,可以理解为重载就可以解决偏特化的需求;

(2)函数模版的基础模版参数是需要传入类型的:template<class T>;

(3)函数模版的特化版本是空的参数类型:template<>.

  • 函数调用规则

(1)优先找非模版函数;

(2)如果没有非模版函数,先找基础模版;

(3)确定了基础模版之后,再看有没有该基础模版的特化版本.

  • 小结

通过以上的函数调用规则,我们可以知道为什么没有调用定义3,而是调用了定义1

(1)定义1和定义2是两个基础模版,所以优先在这两个基础模版里面选择(因为没有非模版函数);

(2)特化的版本是其中一个基础模版的特化版本;

(3)在调试的时候确定如下:函数再调用的时候,选择了定义1的基础模版调用。同时也说明了,定义3是定义2的特化版本,而不是定义1的,都是指针类型,所以还是比较好理解一点;

(4)那么另外一个问题来了,为什么是调用的是定义1的基础模版而不是定义2的基础模版?

另外一个问题

  • 测试

如果我把使用的时候约定参数类型去掉,修改如下:

// 使用
PushToLua(L, result);

这个时候,是能够找到特化模版

template<>
inline void PushToLua(lua_State *L, const char* data) {
    lua_pushstring(L, data);
}

也就是说,这个时候输出的是字符串,而不是userdata。

同样的现象,如果增加一个非模版函数,也是对的:

inline void PushToLua(lua_State *L, const char* data) {
    lua_pushstring(L, data);
}

结果为:

(1)PushToLua (L, result);调用方式不会执行到这个函数,还是执行到值传递(定义1的非指针传递)基础模版类;

(2)如果PushToLua(L, result);调用方式,是优先执行非模版函数的.

  • 猜想

(1)PushToLua (L, result)的调用方式指定了定义1的基础模版,如果PushToLua<R\*>(L, result)会指定定义2的基础模版;

(2)PushToLua(L, result)的调用方式,是按照上面的调用规则走的,也就是传递的R是一个const char*,会匹配到定义2的基础模版,从而发现定义2的基础模版有特化版本,就找到特化版本调用;

(3)这一点可以从int类型的调用看出来,即使是 PushToLua(L, member); 调用一个整形的版本,结果就是一个整形,而不是一个userdata。

解决

  • 1.加一个非模版函数,参数就是字符串指针类型
// 定义4:非模版函数
inline void PushToLua(lua_State *L, const char* data) {
lua_pushstring(L, data);
}
  • 2.使用的时候改成如下方式:
// 修改前
PushToLua<R>(L, result);
// 修改后
PushToLua(L, result);

总结

  • 记住函数模版的调用规则,小心坑
  • PushToLua 使用方式比较特殊,最好还是和正常的函数调用相一致,免生端倪!
  • 关于猜想部分,还没有去研究和确认~

参考

[1]为什么不要特化函数模版? [2] C++模板的特化详解(函数模版特殊,类模版特化)