目录

C++模板元编程的初步介绍和应用

1. 序

本文的主要内容是对C++模板元编程的初步介绍和应用,将围绕luna库进行展开

1.1. luna库简要介绍

luna是一个C++的lua binding库,在我们游戏服务器中有用到,它的核心代码只有一个头文件和一个源文件,实现得非常简洁和优雅。它主要应用了模板元编程的相关技巧,分析待绑定函数的签名信息,自动生成对应的lua绑定函数。阅读本文不需要提前熟悉luna库,只需对lua有初步认识即可

luna库的简单应用如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
static int add(int a, int b) {
  std::cout << "add is called!" << std::endl;
  return a + b;
}

void main() {
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  lua_register_function(L, "add", &add);
  luaL_dostring(L, "print(add(1, 2))");

  lua_close(L);
}
// 程序输出:
// add is called!
// 3

1.2. 总览

本文主要分为两大部分

第一部分会先介绍模板元编程的一些基础概念,基本只会涉及luna库里出现过的模板元编程技巧,不会过度深入

第二部分则是对luna库进行扩展,这也是本文的重点。一直以来模板元编程给人的印象只是炫技,不实用。在这一部分我会应用模板元编程的相关技巧,用60行左右的代码实现一个非常有用的功能:将任意lua函数封装到std::function里,简化C++注册lua回调的方式。这很难用代码生成的方式实现,目前并没有发现其他lua binding库实现了类似功能(老版本的cocos2d-x实现了类似功能,但需要特意用clang插件对C++源码进行语法分析来生成)。本文的最终目标,是扩展luna库实现这样的功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Callback = std::function<std::string(int, float)>;

static Callback callback;

static void set_callback(Callback value) {
  callback = std::move(value);
}

void main() {
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  lua_register_function(L, "set_callback", &set_callback);

  luaL_dostring(L, R"(
    set_callback(function(i, f)
      print(string.format("Print in Lua, args: %d, %f", i, f))
      return "I'm Lua callback"
    end)
  )");
  std::string ret = callback(123, 0.125f);
  std::cout << "Callback result: " << ret << std::endl;

  callback = nullptr;
  lua_close(L);
}

// 程序输出:
// Print in Lua, args: 123, 0.125000
// Callback result : I'm Lua callback

由于我们游戏服务器用到的luna库版本比较老,所以本文对luna库的分析都是基于这个比较老的版本

2. 基础概念

2.1. 什么是模板元编程

模板元编程(节选自wikibook)

Template meta-programming (TMP) refers to uses of the C++ template system to perform computation at compile-time within the code. It can, for the most part, be considered to be “programming with types” — in that, largely, the “values” that TMP works with are specific C++ types.

TMP is much closer to functional programming than ordinary idiomatic C++ is. This is because ‘variables’ are all immutable, and hence it is necessary to use recursion rather than iteration to process elements of a set.

Historically TMP is something of an accident; it was discovered during the process of standardizing the C++ language that its template system happens to be Turing-complete, i.e., capable in principle of computing anything that is computable.

关键字:

  • 是编译期的运算
  • 主要操作的“值”是类型,类型在模板元编程里是First class citizen
  • 类似于函数式编程(“变量”不可变、递归)
  • 是意外被发现的,而不是被发明出来
  • 图灵完全

2.2. 函数与值

2.2.1. 模板元函数

模板元函数(Template meta function)是模板元编程的核心组成部分之一,其本质其实就是模板类,只是看待模板类的角度不一样。所以:

1
2
3
std::vector
std::shared_ptr
std::function

理论上都可以看作是模板元函数(下面简称元函数)

我可以将模板类看作一个“函数”,将模板参数看作是“函数参数”,将模板类生成的类看作是“函数返回值”,即:

1
2
3
4
// 函数:std::vector
// 输入:int
// 输出:std::vector<int>
std::vector<int>

在模板元编程里,主要的输入“数据”是类型、编译期常量和模板类,也就是说,模板元函数:

  • 是一个模板类
  • 输入:类型、编译期常量、模板类
  • 输出:类型

一般来说,我们不会直接使用输出的类型,而是从输出的类型中提取有用的“数据”,这些有用“数据”也是模板元编程的核心组成部分之一

2.2.2. 从输出类型中提取静态常量

这里我们来实现std::is_same元函数,它的作用是判断输入的两个类型是否一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义is_same模板类,它接受两个模板参数T和U,它在类内定义了一个叫value的bool静态常量字段,值总是false
template<class T, class U>
struct is_same {
  static constexpr bool value = false;
};
// 对is_same偏特化,当T和U这两个类型一样时,它在类内定义了一个叫value的bool静态常量字段,值总是true
template<class T>
struct is_same<T, T> {
  static constexpr bool value = true;
};

// 定义一个类型别名,将is_same<int, int>绑定到Result1
// 从模板元编程的角度来看,这里可以看作是调用了元函数is_same,输入类型int和类型int,输出类型is_same<int, int>,赋值给Result1
using Result1 = is_same<int, int>;

// Result1就是类型is_same<int, int>,它有一个叫value的bool静态常量字段
// 由于is_same<int, int>的两个模板参数都是int,所以这里是偏特化后的版本,value的值是true
static_assert(Result1::value);

// 调用元函数is_same,输入类型int和类型float,输出类型is_same<int, float>,赋值给Result2
using Result2 = is_same<int, float>;

// 由于is_same<int, float>的两个模板参数不一样,所以这里没有偏特化,value的值是false
static_assert(!Result2::value);

2.2.3. 从输出类型中提取类型别名

这里我们来实现std::add_pointer元函数,它的作用是为输入的类型T增加一级指针(以下实现不完善,例如没有考虑T为引用的情况)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 定义add_pointer模板类,它接受一个模板参数T,它在类内定义了一个叫type的类型别名,值为T*
template<class T>
struct add_pointer {
  using type = T*;
};

// 调用元函数add_pointer,输入类型int,输出类型add_pointer<int>,赋值给Result
using Result = add_pointer<int>;

// Result就是类型add_pointer<int>,它里面有一个叫type的类型别名,值是int*
static_assert(is_same<int*, Result::type>::value);

2.2.4. 模板元编程与普通编程的差异

模板元编程 普通编程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 元函数调用
using Result = func<arg0, arg1>;

// 取得静态变量字段
Result::value;

// 取得类型别名或内部类
typename Result::type;

// 调用静态方法
Result::method(...);

// 取得类型别名模版或内部类模版
typename Result::template type<T>;

// Result是类型别名,无法修改
//
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 函数调用
auto result = func(arg0, arg1);

// 取得成员变量字段
result.value;

// 没有直接对应
//

// 调用成员方法
result.method(...);

// 没有直接对应
//

// 重新赋值
result = func(arg2, arg3);

2.3. 条件分支

2.3.1. 模板特化

在模板元编程里,我们可以将模板特化看作是一种“条件分支”。上面定义的is_same就已经用了模板特化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 默认情况是false,相当于else分支
template<class T, class U>
struct is_same {
  static constexpr bool value = false;
};
// 当两个模板参数对应的类型相同时,value的值是true,相当于if分支
template<class T>
struct is_same<T, T> {
  static constexpr bool value = true;
};
下面的lua_to_native是从luna库抽出来的代码(未修改):

// lua_to_native函数的作用是将lua对象转换成C++对象
// 默认情况下,会转发给lua_to_object函数进行处理
template <typename T>
T lua_to_native(lua_State* L, int i) { return lua_to_object<T>(L, i); }

// 根据C++的各种基本类型进行特化
template <> inline bool lua_to_native<bool>(lua_State* L, int i) { return lua_toboolean(L, i) != 0; }
template <> inline char lua_to_native<char>(lua_State* L, int i) { return (char)lua_tointeger(L, i); }
template <> inline unsigned char lua_to_native<unsigned char>(lua_State* L, int i) { return (unsigned char)lua_tointeger(L, i); }
template <> inline short lua_to_native<short>(lua_State* L, int i) { return (short)lua_tointeger(L, i); }
template <> inline unsigned short lua_to_native<unsigned short>(lua_State* L, int i) { return (unsigned short)lua_tointeger(L, i); }
template <> inline int lua_to_native<int>(lua_State* L, int i) { return (int)lua_tointeger(L, i); }
template <> inline unsigned int lua_to_native<unsigned int>(lua_State* L, int i) { return (unsigned int)lua_tointeger(L, i); }
template <> inline long lua_to_native<long>(lua_State* L, int i) { return (long)lua_tointeger(L, i); }
template <> inline unsigned long lua_to_native<unsigned long>(lua_State* L, int i) { return (unsigned long)lua_tointeger(L, i); }
template <> inline long long lua_to_native<long long>(lua_State* L, int i) { return lua_tointeger(L, i); }
template <> inline unsigned long long lua_to_native<unsigned long long>(lua_State* L, int i) { return (unsigned long long)lua_tointeger(L, i); }
template <> inline float lua_to_native<float>(lua_State* L, int i) { return (float)lua_tonumber(L, i); }
template <> inline double lua_to_native<double>(lua_State* L, int i) { return lua_tonumber(L, i); }
template <> inline const char* lua_to_native<const char*>(lua_State* L, int i) { return lua_tostring(L, i); }
template <> inline std::string lua_to_native<std::string>(lua_State* L, int i) {
  const char* str = lua_tostring(L, i);
  return str == nullptr ? "" : str;
}
  • 是C++模板的基础功能
  • 只能指定特定类型进行特化,不能进行范围选择以及进一步的条件判断
  • 需要注意:只有模板类(以及C++14的模板变量)才有偏特化,而模板函数只有全特化。由于全特化不够灵活,所以一般来说模板函数不会选择使用模板特化来做条件判断,常用的方案有:tag dispatch、模板类静态函数、std::enable_if和if constexpr

2.3.2. SFINAE

SFINAE
Substitution Failure Is Not An Error(替换失败并非错误)
所谓替换,是指将模板参数代入到模板的过程。在某些情况下,如果替换后会生成无效代码,SFINAE规则规定编译器此时不应该抛出错误,而应该继续尝试其他可用的重载版本

下面的has_member_gc是从luna库抽出来的代码(经过修改,将一些高版本C++才支持的模板元编程技巧替换成更基础的技巧):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// has_member_gc用来判断一个类T是否定义了成员函数void __gc()
template<class T>
struct has_member_gc {
  // 声明辅助模板类sfinae,它接受两个模板参数:U以及U的成员函数指针常量(签名为void())
  template<class U, void (U::*)()>
  struct sfinae;

  // 声明辅助模板函数test,它有一个模板参数U,它有一个函数参数叫unused,unused的类型是sfinae<U, &U::__gc>*,返回类型char
  template<class U>
  static char test(sfinae<U, &U::__gc>* unused);

  // 声明辅助模板函数test的一个重载版本,它有一个模板参数,函数参数是可变参数,返回类型int
  template<class>
  static int test(...);

  // 核心的判断,考虑函数的调用:test<T>(nullptr)
  // test函数有两个重载,根据重载规则,可变参数的重载优先级是最低的,所以会优先考虑第一个重载版本
  // 编译器会先试着实例化sfinae<T, &T::__gc>,这里分两种情况考虑:
  // 1、如果T定义了成员函数void __gc()
  //   则sfinae<T, &T::__gc>是一个合法的类型,最终会调用test的第一个版本,所以test的返回类型是char
  //   这个时候,sizeof(返回类型) == sizeof(char),value的值为true
  // 2、否则
  //   sfinae<T, &T::__gc>不是一个合法的类型,根据SFINAE规则,编译器不会报错,继续去找下一个重载版本
  //   此时会调用到test(...),返回类型是int
  //   这个时候,sizeof(返回类型) != sizeof(char),value的值为false
  static constexpr bool value = sizeof(test<T>(nullptr)) == sizeof(char);
};

struct foo {
  void __gc() {}
};

static_assert(has_member_gc<foo>::value);
static_assert(!has_member_gc<int>::value);
  • 是模板元编程最重要的组成部分之一
  • 当需要判断某个类有没有某个成员变量、成员函数、内部类、内部类型别名等等时,几乎只能用SFINAE(或者用C++20的Concept)
  • 对C++标准的要求很低,C++98也能使用
  • 技巧奇多,代码晦涩难懂,不建议直接使用

2.3.3. std::enable_if

std::enable_if是用SFINAE的方法,根据类型特性有条件地选择对应的重载函数

下面的lua_handle_gc是从luna库抽出来的代码(未修改):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 定义enable_if模板类,它接受两个模板参数Pred和T,Pred是bool常量,T是一个默认为void的类型,它是空类
template<bool Pred, class T = void>
struct enable_if {
};

// 当Pred == true时的偏特化,此时它有一个叫type的类型别名,值为模板参数T
template<class T>
struct enable_if<true, T> {
  using type = T;
};

// 定义lua_handle_gc函数,我们希望:
// 1、默认情况,调用lua_handle_gc(obj)相当于调用delete obj
// 2、如果T定义了成员函数__gc(),则调用lua_handle_gc(obj)相当于调用obj->__gc()
template <class T>
typename enable_if<!has_member_gc<T>::value>::type lua_handle_gc(T* obj) {
  delete obj;
}
template <class T>
typename enable_if<has_member_gc<T>::value>::type lua_handle_gc(T* obj) {
  obj->__gc();
}

struct foo {
  void __gc() {}
};

void main() {
  // 由于foo定义了成员函数__gc(),has_member_gc<foo>::value == true
  // 考虑lua_handle_gc的两个重载,将has_member_gc<foo>::value == true代入
  // 重载1:typename enable_if<false>::type lua_handle_gc(foo* obj);
  //   这里enable_if<false>用的是默认版本,它是一个空类,没有叫type的类型别名,替换失败
  //   根据SFINAE规则,重载1会被抛弃
  // 重载2:typename enable_if<true>::type lua_handle_gc(foo* obj);
  //   这里enable_if<true>用的是偏特化的版本,它有一个叫type的类型别名,值为void,替换成功
  // 所以,只有重载2是合法的,这里最终会调用到重载2的版本
  lua_handle_gc(new foo);

  // 类似地,这里只有重载1是合法的,最终会调用到重载2的版本
  lua_handle_gc(new int);
}
  • 主要用于函数重载
  • C++选择重载函数的规则非常复杂(还需考虑模板函数的特化),enable_if可以指导应该怎么选择
  • 对C++标准的要求很低,C++98也能使用
  • 因为一般来说重载函数的优先级很难控制,所以多个enable_if限定的范围不能重叠,写上去非常繁琐

2.3.4. if constexpr

if constexpr可以理解成是强化版的std::enable_if。在编译期计算if语句的条件值常量,进行选择性编译

用if constexpr修改一下std::enable_if的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <class T>
void lua_handle_gc(T* obj) {
  if constexpr (has_member_gc<T>::value) {
    obj->__gc();
  } else {
    // 假设T没有定义成员函数__gc(),则会进入else分支
    // 如果是普通的if语句,则两个分支都会被编译器编译,但是由于T没有定义成员函数__gc(),会编译出错
    // 而使用if constexpr语句的话,编译器会丢弃其他分支,编译正常通过
    delete obj;
  }
}
  • 可以用类似if语句的方式控制哪部分需要编译以及哪部分丢弃,非常灵活
  • 可以覆盖大部分enable_if的应用场景,甚至比enable_if做得更好
  • 需要C++17的支持

2.4. 模板元编程的相关技术总览

  • C++98
    • sizeof
    • SFINAE
    • tag dispatch
    • boost::mpl (Meta Programming Library)
    • Meta functions
  • C++11
    • constexpr
    • decltype
    • Expression SFINAE
    • Template aliases
    • Trailing function return types
    • Type triats
    • std::enable_if
    • Variadic template
  • C++14
    • decltype(auto)
    • Extended constexpr
    • Variable templates
    • boost::hana
  • C++17
    • if constexpr
    • Fold expressions
  • C++20
    • Concepts

3. luna库扩展

3.1. 支持enum

luna不支持enum,当待绑定的C++函数签名包含enum类型时,会编译出错:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
enum Color { Red, Green, Blue };

static void set_color(Color color) {}

void main() {
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  lua_register_function(L, "set_color", &set_color);
  luaL_dostring(L, "set_color(1)");

  lua_close(L);
}

// oops!编译出错:
// error: 'lua_to_object': no matching overloaded function found

这是因为lua_to_object只对T为指针或引用时做了处理

增加对enum的支持很简单,只需在lua_to_native里加几行代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <class T>
T lua_to_native(lua_State* L, int i) {
  // std::is_enum元函数用来判断T是不是enum
  if constexpr (std::is_enum<T>::value) {
    // std::underlying_type元函数用来获得enum对应的整数类型
    using IntegerType = typename std::underlying_type<T>::type;
    return static_cast<T>(lua_to_native<IntegerType>(L, i));
  } else {
    return lua_to_object<T>(L, i);
  }
}

3.2. 支持注册lua callback

参照对enum的处理,这里实现的关键是在lua_to_native函数里增加对std::function的判断和处理

3.2.1. lua_to_native里增加对std::function的判断和处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
template <class T>
T lua_to_native(lua_State* L, int i) {
  if constexpr (is_std_function<T>::value) {
    // 如果T是std::function,则构建一个lua_function_adapter函数对象放到std::function里返回
    // 当lua_function_adapter被调用时,会调用到对应的lua函数
    return lua_function_adapter<T>(L, i);
  } else if constexpr (std::is_enum<T>::value) {
    using IntegerType = typename std::underlying_type<T>::type;
    return static_cast<T>(lua_to_native<IntegerType>(L, i));
  } else {
    return lua_to_object<T>(L, i);
  }
}

3.2.2. 实现is_std_function元函数

is_std_function元函数用来判断T是不是std::function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 默认情况是false
template <class T>
struct is_std_function {
  static constexpr bool value = false;
};

// std::function<Ret(Args...)>偏特化,值是true
template <class Ret, class... Args>
struct is_std_function<std::function<Ret(Args...)>> {
  static constexpr bool value = true;
};

3.2.3. 实现lua_function_adapter函数对象

lua_function_adapter是一个函数对象,当它被调用时,会调用到预先注册好的lua函数里

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 这个类用来保存lua主线程lua_State以及lua callback在注册表的位置
struct lua_function_holder {
  lua_State* main_state;
  int reg_index;

  lua_function_holder(lua_State* L, int i) {
    // 保存和恢复L的栈顶位置
    lua_guard _(L);

    // 取出lua主线程的lua_State压入栈,因为传进来的L有可能是一个子协程
    lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);

    // 保存下主线程的lua_State
    main_state = lua_tothread(L, -1);

    // 将lua callback复制一份压入栈
    lua_pushvalue(L, i);

    // 将lua callback注册到注册表,位置保存在reg_index
    reg_index = luaL_ref(main_state, LUA_REGISTRYINDEX);
  }

  ~lua_function_holder() {
    // 注销注册表里的lua callback
    luaL_unref(main_state, LUA_REGISTRYINDEX, reg_index);
  }
};

// 适配器类的声明,没必要实现
template <class T>
struct lua_function_adapter;

// std::function<Ret(Args...)>偏特化
template <class Ret, class... Args>
struct lua_function_adapter<std::function<Ret(Args...)>> {
  // lua_function_adapter最终会放到std::function里
  // 而std::function要求函数对象必须CopyConstructible
  // 所以这里用了std::shared_ptr保证资源释放
  std::shared_ptr<lua_function_holder> holder;

  lua_function_adapter(lua_State* L, int i)
    : holder(std::make_shared<lua_function_holder>(L, i)) {}

  Ret operator()(Args... args) {
    lua_State* L = holder->main_state;

    // 保存和恢复L的栈顶位置
    lua_guard _(L);

    // 从注册表取出lua callback
    lua_rawgeti(L, LUA_REGISTRYINDEX, holder->reg_index);

    // 将C++参数转换成lua对象,这里最终会展开成:
    // int unused[] = { 0, (native_to_lua(L, arg0), 0), (native_to_lua(L, arg1), 0), (native_to_lua(L, arg2), 0), ... };
    // 作用相当于依次调用:native_to_lua(L, arg0), native_to_lua(L, arg1), native_to_lua(L, arg2), ...
    // 这种用法在luna库源码的其他地方也有用到
    // 使用C++17 Fold expressions可以简化为以下形式:
    // (native_to_lua(L, args), ...);
    int unused[] = { 0, (native_to_lua(L, args), 0)... };

    if constexpr (!std::is_same<Ret, void>::value) {
      // 调用lua callback,返回一个值
      lua_call(L, sizeof...(Args), 1);

      // 将返回值转换成C++对象
      return lua_to_native<Ret>(L, 1);
    }
    else {
      // 调用lua callback,没有返回值
      lua_call(L, sizeof...(Args), 0);
    }
  }
};

3.2.4. 应用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Callback = std::function<std::string(int, float)>;

static Callback callback;

static void set_callback(Callback value) {
  callback = std::move(value);
}

void main() {
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  lua_register_function(L, "set_callback", &set_callback);

  // 设置lua callback
  luaL_dostring(L, R"(
    set_callback(function(i, f)
      print(string.format("Print in Lua, args: %d, %f", i, f))
      return "I'm Lua callback"
    end)
  )");
  std::string ret = callback(123, 0.125f);
  std::cout << "Callback result: " << ret << std::endl;

  // 设置C++ callback
  set_callback([](int i, float f) {
    std::cout << "Print in C++, args: " << i << ", " << f << std::endl;
    return "I'm C++ callback";
  });
  ret = callback(456, -8.f);
  std::cout << "Callback result: " << ret << std::endl;

  callback = nullptr;
  lua_close(L);
}

// 程序输出:
// Print in Lua, args: 123, 0.125000
// Callback result : I'm Lua callback
// Print in C++, args : 456, -8
// Callback result : I'm C++ callback

4. 进阶

4.1. lua callback支持返回多个值

应用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Callback = std::function<std::tuple<int, float, std::string>(int, float)>;

static Callback callback;

static void set_callback(Callback value) {
  callback = std::move(value);
}

void main() {
  lua_State* L = luaL_newstate();
  luaL_openlibs(L);

  lua_register_function(L, "set_callback", &set_callback);

  luaL_dostring(L, R"(
    set_callback(function(i, f)
      print(string.format("Print in Lua, args: %d, %f", i, f))
      return i + 100, f * 8, "I'm Lua callback"
    end)
  )");
  auto [res0, res1, res2] = callback(123, 0.125f);
  std::cout << "Callback result: " << res0 << ", " << res1 << ", " << res2 << std::endl;

  callback = nullptr;
  lua_close(L);
}

// 程序输出:
// Print in Lua, args: 123, 0.125000
// Callback result : 223, 1, I'm Lua callback

核心实现代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
template <class T>
struct is_tuple {
  static constexpr bool value = false;
};
template <class... Ts>
struct is_tuple<std::tuple<Ts...>> {
  static constexpr bool value = true;
};

template <class T>
struct tag {};

template <class... Ts, size_t... Is>
std::tuple<Ts...> lua_to_tuple(lua_State* L, int i, tag<std::tuple<Ts...>>, luna_sequence<Is...>) {
  return { lua_to_native<Ts>(L, i + Is)... };
};

template <class Ret, class... Args>
struct lua_function_adapter<std::function<Ret(Args...)>> {
  std::shared_ptr<lua_function_holder> holder;

  lua_function_adapter(lua_State* L, int i)
    : holder(std::make_shared<lua_function_holder>(L, i)) {}

  Ret operator()(Args... args) {
    lua_State* L = holder->main_state;
    lua_guard _(L);
    lua_rawgeti(L, LUA_REGISTRYINDEX, holder->reg_index);
    int unused[] = { 0, (native_to_lua(L, args), 0)... };

    if constexpr (is_tuple<Ret>::value) {
      // tuple的元素个数
      constexpr std::size_t TUPLE_SIZE = std::tuple_size<Ret>::value;

      // 调用lua callback,返回TUPLE_SIZE个值
      lua_call(L, sizeof...(Args), TUPLE_SIZE);

      // 将返回的多个lua对象转换成std::tuple
      return lua_to_tuple(L, 1, tag<Ret>{}, make_luna_sequence<TUPLE_SIZE>{});
    }else if constexpr (!std::is_same<Ret, void>::value) {
      lua_call(L, sizeof...(Args), 1);
      return lua_to_native<Ret>(L, 1);
    }else {
      lua_call(L, sizeof...(Args), 0);
    }
  }
};

4.2. 编译器只支持C++11时的替换方案:不使用if constexpr,改用tag dispatch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
template <class T>
struct tag {};

// 定义lua_to_native_impl函数的多个重载,使用tag dispatch做条件判断
// 注意这里没有使用模板特化
template <class T>
T lua_to_native_impl(lua_State* L, int i, tag<T>, ...) {
    return lua_to_object<T>(L, i);
}
template <class Ret, class... Args>
std::function<Ret(Args...)> lua_to_native_impl(lua_State* L, int i, tag<std::function<Ret(Args...)>>) {
    return lua_function_adapter<std::function<Ret(Args...)>>(L, i);
}
template <class T>
typename std::enable_if<std::is_enum<T>::value, T>::type lua_to_native_impl(lua_State* L, int i, tag<T>) {
    using IntegerType = typename std::underlying_type<T>::type;
    return static_cast<T>(lua_to_native<IntegerType>(L, i));
}
inline bool lua_to_native_impl(lua_State* L, int i, tag<bool>) {
    return lua_toboolean(L, i) != 0;
}
inline char lua_to_native_impl(lua_State* L, int i, tag<char>) {
    return (char)lua_tointeger(L, i);
}
inline unsigned char lua_to_native_impl(lua_State* L, int i, tag<unsigned char>) {
    return (unsigned char)lua_tointeger(L, i);
}
inline short lua_to_native_impl(lua_State* L, int i, tag<short>) {
    return (short)lua_tointeger(L, i);
}
inline unsigned short lua_to_native_impl(lua_State* L, int i, tag<unsigned short>) {
    return (unsigned short)lua_tointeger(L, i);
}
inline int lua_to_native_impl(lua_State* L, int i, tag<int>) {
    return (int)lua_tointeger(L, i);
}
inline unsigned int lua_to_native_impl(lua_State* L, int i, tag<unsigned int>) {
    return (unsigned int)lua_tointeger(L, i);
}
inline long lua_to_native_impl(lua_State* L, int i, tag<long>) {
    return (long)lua_tointeger(L, i);
}
inline unsigned long lua_to_native_impl(lua_State* L, int i, tag<unsigned long>) {
    return (unsigned long)lua_tointeger(L, i);
}
inline long long lua_to_native_impl(lua_State* L, int i, tag<long long>) {
    return lua_tointeger(L, i);
}
inline unsigned long long lua_to_native_impl(lua_State* L, int i, tag<unsigned long long>) {
    return (unsigned long long)lua_tointeger(L, i);
}
inline float lua_to_native_impl(lua_State* L, int i, tag<float>) {
    return (float)lua_tonumber(L, i);
}
inline double lua_to_native_impl(lua_State* L, int i, tag<double>) {
    return lua_tonumber(L, i);
}
inline const char* lua_to_native_impl(lua_State* L, int i, tag<const char*>) {
    return lua_tostring(L, i);
}
inline std::string lua_to_native_impl(lua_State* L, int i, tag<std::string>) {
    const char* str = lua_tostring(L, i);
    return str == nullptr ? "" : str;
}

// lua_to_native函数,用tag将T的类型传递给lua_to_native_impl
template <class T>
T lua_to_native(lua_State* L, int i) {
    return lua_to_native_impl(L, i, tag<T>{});
}