前言
我相信你是遇到了同样的问题、通过搜索引擎来到这里的。为了不耽误排查问题的时间,我提前说明一下这篇文章所描述的问题范畴:
- 我遇到的问题和 c++ 模板相关;
- 如果我减少传递的参数的话,是有可能避免这个编译错误的;
- 和我使用的 VS 开发环境版本相关,我使用 VS2013 时报错,但是使用 VS2015 及以上版本就不报错;
- 和我使用的平台也相关,如果我改用 g++ 编译则不报错(gcc 版本为 4.9.2)。
如果这不是你的场景,或者通过上述几种方法(本质上都是提高 c++ 编译器版本)可以解决你的问题,就没有必要浪费时间继续看了。因为其实本文也没有找到彻底解决这种编译错误的方法,只是做了一些探讨。
问题的背景
在项目中需要操作本地的一个 sqlite 数据库,我并没有直接使用 sqlite3 的 c 接口,而是使用了一个叫做 qtl 的 c++ 的模板类库。具体到查询数据库,可以抽离下面的代码做为示例:
1 class popbox_msg_t 2 { 3 public: 4 int msgtype = 0; 5 int status = 0; // 1:ok; 0:fail 6 int count = 0; // retry times 7 time_t stamp = 0; // receive time 8 std::string msgid; 9 std::string msgbody; 10 }; 11 12 template <class OutputIterator> 13 int db_read_popbox_msg(OutputIterator it) 14 { 15 int ret = 0; 16 qtl::sqlite::database db(SQLITE_TIMEOUT); 17 18 try 19 { 20 // sql to create table: 21 // create table popmsg (msgid text not null, msgtype integer not null, status integer not null, count integer not null, 22 // msgbody text not null, stamp timestamp not null, primary key (msgid, msgtype)); 23 #ifdef WIN32 24 db.open("C:\\ProgramData\\xxxxxx\\xxx\\xx\\gcm.db", NULL); 25 #else 26 db.open("/etc/xxxxxx/xxx/gcm.db", NULL); 27 #endif 28 printf("open db for read popbox msg OK\n"); 29 db.query("select msgid, msgtype, status, count, msgbody, stamp from popmsg order by msgtype", 30 [&ret, &it](std::string const& msgid, int msgtype, int status, int count, std::string const& msgbody, time_t stamp) { 31 popbox_msg_t pm; 32 pm.msgid = msgid; 33 pm.msgtype = msgtype; 34 pm.status = status; 35 pm.count = count; 36 pm.msgbody = msgbody; 37 pm.stamp = stamp; 38 39 *it = pm; 40 ++ret; 41 return true; 42 }); 43 44 db.close(); 45 printf("add %d popbox msg into delay queue\n", ret); 46 } 47 catch (qtl::sqlite::error &e) 48 { 49 printf("manipute db for resend popbox msg error %d: %s\n", e.code(), e.what()); 50 db.close(); 51 return -1; 52 } 53 54 return ret; 55 } 56 57 58 int main(int argc, char* argv[]) 59 { 60 std::vector <popbox_msg_t> vec; 61 db_read_popbox_msg(std::back_inserter(vec)); 62 printf("got %d iterms from db\n", vec.size()); 63 return 0; 64 }
简单解释一下,这段代码从数据库表中读取相应的记录存放在消息容器中 ,方便后面进一步处理(关于模板函数 db_read_popbox_msg 的一些细节,可以参考我之前写过的这篇文章:《如何优雅的传递 stl 容器作为函数参数来实现元素插入和遍历? 》)。这里主要是用到了 qtl:sqlite::database 对象的 query 接口,它有很多重载,这里使用的是包含一个 lambda 表达式来处理返回数据的接口,它们的声明如下:
1 void qtl::base_database<T, Command>::query<Params, ValueProc>(const std::string & query_text, const Params & params, ValueProc && proc); 2 void qtl::base_database<T, Command>::query<Params, ValueProc>(const char * query_text, const Params & params, ValueProc && proc); 3 void qtl::base_database<T, Command>::query<Params, ValueProc>(const char * query_text, size_t text_length, const Params & params, ValueProc && proc); 4 void qtl::base_database<T, Command>::query<ValueProc>(const std::string & query_text, ValueProc && proc); 5 void qtl::base_database<T, Command>::query<ValueProc>(const char * query_text, ValueProc && proc); 6 void qtl::base_database<T, Command>::query<ValueProc>(const char * query_text, size_t text_length, ValueProc && proc);
具体就是第 5 个声明啦,其中 params 表示提供给 sql 语句中作为占位符 ‘?’ 的参数,用来预防 sql 注入的问题,这里我们没有输入任何参数,所以没有用到。这段代码是可以编译通过的,执行也没有问题,能从数据库中读取到数据。
问题的提出
问题出现在当我发现有时候需要根据产品名称和登录用户名称筛选记录时,这两个字段的信息本来是存放在 msgbody 的 json 字段中,现在需要将它们提取出来放在数据库表的列里。为此我需要增加两个字段 cid 与 uid :
1 class popbox_msg_t 2 { 3 public: 4 int msgtype = 0; 5 int status = 0; // 1:ok; 0:fail 6 int count = 0; // retry times 7 time_t stamp = 0; // receive time 8 std::string msgid; 9 std::string msgbody; 10 std::string cid; 11 std::string uid; 12 }; 13 14 template <class OutputIterator> 15 int db_read_popbox_msg(OutputIterator it) 16 { 17 int ret = 0; 18 qtl::sqlite::database db(SQLITE_TIMEOUT); 19 20 try 21 { 22 // sql to create table: 23 // create table popmsg (msgid text not null, msgtype integer not null, cid text not null, uid text not null, 24 // status integer not null, count integer not null, msgbody text not null, stamp timestamp not null, primary key (msgid, msgtype, cid, uid)); 25 #ifdef WIN32 26 db.open("C:\\ProgramData\\xxxxxx\\xxx\\xx\\gcm.db", NULL); 27 #else 28 db.open("/etc/xxxxxx/xxx/gcm.db", NULL); 29 #endif 30 printf("open db for resend popbox msg OK\n"); 31 db.query("select msgid, msgtype, cid, uid, status, count, msgbody, stamp from popmsg order by msgtype", 32 [&ret, &it](std::string const& msgid, int msgtype, std::string const& cid, std::string const& uid, int status, int count, std::string const& msgbody, time_t stamp) { 33 popbox_msg_t pm; 34 pm.msgid = msgid; 35 pm.msgtype = msgtype; 36 pm.cid = cid; 37 pm.uid = uid; 38 pm.status = status; 39 pm.count = count; 40 pm.msgbody = msgbody; 41 pm.stamp = stamp; 42 43 *it = pm; 44 ++ret; 45 return true; 46 }); 47 48 db.close(); 49 printf("add %d popbox msg into delay queue\n", ret); 50 } 51 catch (qtl::sqlite::error &e) 52 { 53 printf("manipute db for resend popbox msg error %d: %s\n", e.code(), e.what()); 54 db.close(); 55 return -1; 56 } 57 58 return ret; 59 } 60 61 62 int main(int argc, char* argv[]) 63 { 64 std::vector <popbox_msg_t> vec; 65 db_read_popbox_msg(std::back_inserter(vec)); 66 printf("got %d iterms from db\n", vec.size()); 67 return 0; 68 }
本来以为会顺利通过编译,没想到报了下面的编译错误:
1>------ 已启动生成: 项目: test-qtl, 配置: Debug Win32 ------ 1> test-qtl.cpp 1>f:\xxxxxxxxx\src\include\qtl\apply_tuple.h(17): fatal error C1045: 编译器限制 : 链接规范嵌套太深 ========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
一开始我还怀疑是别的地方出了问题,为了验证不是因为 lambda 表达式加参数导致的,我把那两个参数临时去掉了,结果就顺利编译通过了!郁闷ing…
错误分析
只好硬着头皮看这个错误本身到底是什么东东,经过一番百度,在微软官方网页得到了“详尽”的说明:
编译器限制 : 链接规范嵌套太深 嵌套的外部对象超过编译器限制。 允许嵌套的外部链接类型,如 extern "c++"。 减少嵌套的外部项的数量以解决该错误。
聊胜于无,不过还真有网友使用 9 层嵌套的 extern “C” 在 VS2005 上模拟出了这个错误。我检查了一下代码,并没有发现 extern ”C” 或 “C++” 这些东西,所以还是不明就里。现在焦点集中在了报错的文件 apply_tuple.h (17) 上,找到这个文件并定位到错误位置:
1 #ifndef _APPLY_TUPLE_H_ 2 #define _APPLY_TUPLE_H_ 3 4 #include <stddef.h> 5 #include <tuple> 6 #include <type_traits> 7 #include <utility> 8 9 namespace detail 10 { 11 template<size_t N> 12 struct apply 13 { 14 template<typename F, typename T, typename... A> 15 static inline auto apply_tuple(F&& f, T&& t, A&&... a) 16 -> decltype(apply<N-1>::apply_tuple( 17 std::forward<F>(f), std::forward<T>(t), 18 std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)... 19 )) 20 { 21 return apply<N-1>::apply_tuple(std::forward<F>(f), std::forward<T>(t), 22 std::get<N-1>(std::forward<T>(t)), std::forward<A>(a)... 23 ); 24 } 25 }; 26 27 template<> 28 struct apply<0> 29 { 30 template<typename F, typename T, typename... A> 31 static inline typename std::result_of<F(A...)>::type apply_tuple(F&& f, T&&, A&&... a) 32 { 33 return std::forward<F>(f)(std::forward<A>(a)...); 34 } 35 }; 36 } 37 38 template<typename F, typename T> 39 inline auto apply_tuple(F&& f, T&& t) 40 -> decltype(detail::apply< std::tuple_size< 41 typename std::decay<T>::type 42 >::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t))) 43 { 44 return detail::apply< std::tuple_size< 45 typename std::decay<T>::type 46 >::value>::apply_tuple(std::forward<F>(f), std::forward<T>(t)); 47 } 48 49 #endif //_APPLY_TUPLE_H_
看得我直发晕,说实话模板我也就能实例化用一下,真使用模板写个库什么的,我还是算了……不过我大概知道 tuple 是标准库里用来构造结构体的,例如:
1 int abc = 123; 2 std::string str = "abc"; 3 auto t = std::make_tuple(abc, str, "hello"); 4 printf ("value = %d, %s, %s\n", std::get<0> (t), std::get<1>(t).c_str(), std::get<2>(t));
这段代码构造了一个包含三个成员的结构体,成员类型分别为 int / string / char const*, 后续可以使用 std::get<N> 来访问各个成员。tuple 本身可以容纳的字段数量是不受限制的,这个特性使得它广泛的应用于可变模板参数(…)中,用来将不确定数量的参数压缩到一个 tuple 中,便于后续处理。知道了 tuple,那这个 apply_tuple.h 又是做什么的呢?通读上面的代码,基本可以确定以下几点:
- apply 是一个模板类(结构体),它有一个 apply_tuple 静态方法;
- apply::apply_tuple 静态方法返回的是类型 F 与类型 A 的组合,很像函数调用的形式(也可能是重载了括号运算符的类);
- apply 的声明含有模板递归,通过递归可以将传递给它的 N 个参数最终化解为 apply<0>,而这个是有明确定义的(即递归终点);
- 全局 apply_tuple 是一个模板函数,返回的是 apply::apply_tuple 的返回类型。
总结一下,就是经过 apply_tuple 处理后的 F 与 T 类型的参数 f 和 t,最终会变成 f (t1,t2,t3…) 的调用形式。非常类似 std::make_pair 之于 std::pair 及 std::make_tuple 之于 std::tuple,模板函数的作用就是简化模板类的使用,可以根据参数自动推导模板类各个模板参数的类型,从而简化书写。不过与上面两个不同的地方在于,apply_tuple 并非生成 tuple,而是将 tuple 中各个字段提取出来,最终交给 F f 去调用。可能有的人不信,那好,我们再看下 make_tuple 的调用点:
1 template<typename F, typename... Types> 2 struct apply_impl<F, std::tuple<Types...>> 3 { 4 private: 5 typedef typename std::remove_reference<F>::type fun_type; 6 typedef std::tuple<Types...> arg_type; 7 typedef typename std::result_of<F(Types...)>::type raw_result_type; 8 template<typename Ret, bool> 9 struct impl {}; 10 template<typename Ret> 11 struct impl<Ret, true> 12 { 13 typedef bool result_type; 14 result_type operator()(F&& f, arg_type&& v) 15 { 16 apply_tuple(std::forward<F>(f), std::forward<arg_type>(v)); 17 return true; 18 } 19 }; 20 template<typename Ret> 21 struct impl<Ret, false> 22 { 23 typedef Ret result_type; 24 result_type operator()(F&& f, arg_type&& v) 25 { 26 return apply_tuple(std::forward<F>(f), std::forward<arg_type>(v)); 27 } 28 }; 29 30 public: 31 typedef typename impl<raw_result_type, std::is_void<raw_result_type>::value>::result_type result_type; 32 result_type operator()(F&& f, arg_type&& v) 33 { 34 return impl<raw_result_type, std::is_void<raw_result_type>::value>()(std::forward<F>(f), std::forward<arg_type>(v)); 35 } 36 };
整个 qtl 库中,唯二的两个调用点就在上面啦。可以看到它接收的第二个参数 v 是 arg_type 类型的,而这个又是 std::tuple<Types…> 的重定义。看来 qtl 在把我们的 lambda 表达式折叠成 tuple 后,又在这里展开、调用,起到了将查询到的各个参数传递给回调函数的目的。有的人可能又会问了,那它是怎么知道 tuple 包含多少字段进而展开的呢?毕竟 apply 类型是需要 N 这个模板参数进行递归展开的呀!细心的同学可能早就注意到 apply_tuple.h (40-45)这两行包含的 std::tuple_size 类型了,不错,标准库已经为我们提供了获取一个 tuple 字段总数的方法了。至此,我大概明白了为什么会出错了,可能就是在操作 tuple 的过程中,由于使用模板递归会生成大量的中间类型,当参数数量达到一定限度时,可能会引起过度的类型嵌套,进而触发 C1045 这个编译错误。
问题的解决
当时我还没有将代码简化成一个小的 demo 去验证,在原始的工程项目里我怀疑是类型使用了命名空间,这样可能在类型嵌套过程中包含了太多 namespace 导致编译错误?为了验证我的想法,我急需知道 template 实例化后的代码情况,对于预处理我知道在 VS 里可以通过 /P 选项生成 .i 后缀的中间文件来查看,那么对于模板实例化,有没有什么选项或工具可以查看实例化后的代码呢?如果可以的话,我就能知道是什么语法元素导致的嵌套过度了(进而去除之)。
查看模板实例化中间结果
首先使用 /P 选项是不行的啦,经过验证这种方法只对宏有效,模板还是原样不变的呈现在中间结果中。经过一轮新的百度,我得到下面几个有用的信息:
- 专门的模板调试库 templight;
- 不同 vs 版本的编译器允许的嵌套限制值可能不同;
- g++ 支持一个 -frepo 编译参数,可以查看实例化后的函数链接,vc 没有找到对应的选项。
对于 templight,简单看了下,不太好上手,而且好像主要集中在处理模板展开时性能瓶颈排查这方面的问题,与我想看展开后的源码的目标不符,没有进一步深入研究;
对于使用高版本的 VS,我这里刚好装了 VS2015,试了下果然好了。但这个只是绕开了问题,并没有解决问题,而且我的项目只能使用 VS2013(2015 需要带一坨 dll,特别零碎),所以也 pass;
对于使用 g++ 编译,我这里倒是有现成的环境,而且如果能找到导致嵌套层次增加的语法因素,加以修改后再应用到我的项目里,也还是一个可以接受的方案。于是先有了上面的 demo,然后又为它在 linux 上配置了 cmake 编译文件:
1 $ cat CMakeLists.txt 2 cmake_minimum_required(VERSION 3.0) 3 project(test_qtl) 4 include_directories(../include) 5 set(CMAKE_CXX_FLAGS "-std=c++11 -pthread -g -Wall ${CMAKE_CXX_FLAGS}") 6 #set(CMAKE_CXX_FLAGS "-std=c++11 -frepo -pthread -g -Wall ${CMAKE_CXX_FLAGS}") 7 #set(CMAKE_CXX_FLAGS "-std=c++11 -save-temps -pthread -g -Wall ${CMAKE_CXX_FLAGS}") 8 link_directories(${PROJECT_SOURCE_DIR}/../bin) 9 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin) 10 11 add_executable (test-qtl test-qtl.cpp) 12 target_link_libraries(test-qtl sqlite3)
注意需要链接 sqlite3 的库,幸好之前项目做跨平台编译,这些已经都具备了。正常编译,出乎意料的直接通过了,而且能正常运行,看来新版本的 c++ 编译器都放宽了嵌套数量的限制。不管这些,直接上 -frepo 编译,网上说会生成一个 .rpo 后缀的文件,找了半天没找到,上搜索:
$ find . -name '*.rpo' -type f ./CMakeFiles/test-qtl.dir/test-qtl.cpp.rpo
居然在这么深的地方,打开一看让人后背发凉。内容比较多,这里过滤一下我要找的函数:
$ cat CMakeFiles/test-qtl.dir/test-qtl.cpp.rpo | grep db_read_popbox_msg O _Z20db_read_popbox_msgISt20back_insert_iteratorISt6vectorI12popbox_msg_tSaIS2_EEEEiT_
这东西就是一个链接名称,根本没有我要找的模板展开后的源码,大失所望!回头再看下 gcc 关于 -frepo 的说明:
-frepo Enable automatic template instantiation at link time. This option also implies -fno-implicit-templates.
和没说一样。CMakeLists 文件第 7 行另外还验证了一下网友说的 -save-temps 选项,结果会生成一个 .ii 结尾的文件,打开一看和 VS 的 /P 选项差不多,都是对宏进行预处理的中间结果,模板压根没有展开。至此,走查看模板展开中间结果的路子基本就到死胡同了,我只能得出一个结论:c++ 对模板调试的支持度不好。毕竟模板这个东西好像就是 c++ 语言发展过程中的一个意外收获,并不是预先经过设计实现的,难免有一些粗糙啊。
回归源码
上面的路行不通,而我又不能切换 VS 版本,那只好再硬着头皮回来检查源码了,看看有什么办法绕过这个问题没有。上回说到,这个问题其实和 tuple 密切相关,这让我突然想到,如果我抛开所有一切,只是构造一个复杂的 tuple,会不会复现这个编译错误呢?说干就干 ,于是有了下面这段代码:
1 popbox_msg_t pm; 2 pm.stamp = 1; 3 auto t = std::make_tuple(pm.msgid, pm.msgtype, pm.cid, pm.uid, pm.status, pm.count, pm.msgbody, pm.stamp); 4 printf("stamp = %d\n", std::get<7>(t));
为了逼真还原 demo,我直接使用了 popbox_msg_t 这个类型的各个字段,当然了,也可以直接写原始类型来获取更通用的代码示例。编译居然正常通过,而且执行也没有问题,打印 stamp 的值为 1。看来问题不是出在生成 tuple 的过程中,我加了一行代码,再来试一下展开的过程:
1 int lambda_func(std::string const& msgid, int msgtype, std::string const& cid, std::string const& uid, int status, int count, std::string const& msgbody, time_t stamp) 2 { 3 return 0; 4 } 5 6 void test() 7 { 8 popbox_msg_t pm; 9 pm.stamp = 1; 10 auto t = std::make_tuple(pm.msgid, pm.msgtype, pm.cid, pm.uid, pm.status, pm.count, pm.msgbody, pm.stamp); 11 printf("stamp = %d\n", std::get<7>(t)); 12 apply_tuple(lambda_func, t); 13 }
调用 qtl 的 apply_tuple 时,需要提供一个函数,这里直接写了一个空的 lambda_func 充数。再编译一下,果然报错了。看来问题就出在 tuple 展开过程中。
解铃还需系铃人
正在我一筹莫展的当口,一篇介绍 qtl 的文章让我眼前一亮:
1 struct TestMysqlRecord 2 { 3 uint32_t id; 4 char name[33]; 5 qtl::mysql::time create_time; 6 7 TestMysqlRecord() 8 { 9 memset(this, 0, sizeof(TestMysqlRecord)); 10 } 11 }; 12 13 namespace qtl 14 { 15 template<> 16 inline void bind_record<qtl::mysql::statement, TestMysqlRecord>(qtl::mysql::statement& command, TestMysqlRecord&& v) 17 { 18 qtl::bind_field(command, 0, v.id); 19 qtl::bind_field(command, 1, v.name); 20 qtl::bind_field(command, 2, v.create_time); 21 } 22 } 23 24 db.query("select * from test where id=?", 25 id, TestMysqlRecord(), 26 [](TestMysqlRecord& record) { 27 printf("ID=\"%d\", Name=\"%s\"\n", record.id, record.name); 28 return true; 29 });
这个例子说可以把数据库表中各列数据绑定到结构的各个成员上,查询的时候,将直接返回对应的结构体。这样一来,首先 lambda 表达式的参数将减少很多;其次有了绑定关系后,也不需要再自己构造 tuple 了,上面的逻辑就可以被绕过。抱着试一试的心态,我把之前的 demo 改造成下面的样子:
1 class popbox_msg_t 2 { 3 public: 4 int msgtype = 0; 5 int status = 0; // 1:ok; 0:fail 6 int count = 0; // retry times 7 time_t stamp = 0; // receive time 8 std::string msgid; 9 std::string msgbody; 10 std::string cid; 11 std::string uid; 12 }; 13 14 namespace qtl 15 { 16 template<> 17 inline void bind_record<qtl::sqlite::statement, popbox_msg_t>(qtl::sqlite::statement& command, popbox_msg_t&& v) 18 { 19 int n = 0; 20 qtl::bind_field(command, n++, v.msgid); 21 qtl::bind_field(command, n++, v.msgtype); 22 qtl::bind_field(command, n++, v.cid); 23 qtl::bind_field(command, n++, v.uid); 24 qtl::bind_field(command, n++, v.status); 25 qtl::bind_field(command, n++, v.count); 26 qtl::bind_field(command, n++, v.msgbody); 27 qtl::bind_field(command, n++, v.stamp); 28 } 29 } 30 31 template <class OutputIterator> 32 int db_read_popbox_msg(OutputIterator it) 33 { 34 int ret = 0; 35 qtl::sqlite::database db(SQLITE_TIMEOUT); 36 37 try 38 { 39 // sql to create table: 40 // create table popmsg (msgid text not null, msgtype integer not null, cid text not null, uid text not null, 41 // status integer not null, count integer not null, msgbody text not null, stamp timestamp not null, primary key (msgid, msgtype, cid, uid)); 42 #ifdef WIN32 43 db.open("C:\\ProgramData\\xxxxxx\\xxx\\xx\\gcm.db", NULL); 44 #else 45 db.open("/etc/xxxxxx/xxx/gcm.db", NULL); 46 #endif 47 printf("open db for resend popbox msg OK\n"); 48 db.query("select msgid, msgtype, cid, uid, status, count, msgbody, stamp from popmsg order by msgtype", 49 [&ret, &it](popbox_msg_t const& pm) { 50 *it = pm; 51 ++ret; 52 return true; 53 }); 54 55 56 db.close(); 57 printf("add %d popbox msg into delay queue\n", ret); 58 } 59 catch (qtl::sqlite::error &e) 60 { 61 printf("manipute db for resend popbox msg error %d: %s\n", e.code(), e.what()); 62 db.close(); 63 return -1; 64 } 65 66 return ret; 67 } 68 69 70 int main(int argc, char* argv[]) 71 { 72 std::vector <popbox_msg_t> vec; 73 db_read_popbox_msg(std::back_inserter(vec)); 74 printf("got %d iterms from db\n", vec.size()); 75 return 0; 76 }
主要的变化分为两部分,一是 line 14-30 增加了绑定关系的代码,注意这个需要写在 qtl 命名空间下,防止模板特化时找不到对应的定义;二是 line 49,相比之前简洁很多。编译一下,顺利通过!这种方式还有一个好处,就是增删查询的字段时,回调点不用做任何修改,只需要修改结构体成员和绑定关系即可。
下载
demo 依赖 qtl 库,这里提供一个 qtl 的 git 地址供大家围观:https://github.com/goodpaperman/qtl
我使用的 qtl 版本貌似还不是最新的,我用最新的版本试了下,有一些其它编译错误(sqlite 头文件找不到、min表达式找不到等),把这些问题解决后,C1045 这个问题仍然存在。我在 demo 里也携带了一份旧版本的 qtl 库,方便编译、测试。这个 demo 本身没有 git 地址可供下载,因为它仅仅是一个错误演示而已,我把它打包成 zip 上传到博客园了,可以 点击这里 下载。
demo 也可以在 linux 上编译、运行,这里提供了 cmake 的配置文件及其生成的 Makefile 文件。不论哪个平台,其中需要用到的头文件 (qtl 与 sqlite3)、库文件(sqlite3 及 msvc 运行库)这里都包含了,可以直接编译。同时也提供了预先编译好的可执行文件,在 Win10 32 位及 linux 64 位系统上可以直接运行。此外还提供了用于演示的 sqlite 数据库(gcm.db),里面包含一些测试数据,如果能正常运行,则在控制台可以看到下面的输出:
结语
回顾一下这个问题,其实并没有从根本上解决 lambda 表达式参数过多导致报错的问题。而且很奇怪为什么标准库在生成 tuple 过程中就没问题,而 qtl 在展开相同大小 tuple 的过程中就出了问题,可见 qtl 的代码质量和标准库还是有差距啊。说了 qtl 这么多不好,那么它有没有优点呢?当然是有的!不过限于篇幅,这里就不展开介绍了,这个话题后期可以单独写一篇文章。
参考
[1]. 能否通过 编译器设置 或其它方法 屏蔽或消除 MS VC C1045 错误?
[2]. fatal error C1061: 编译器限制 : 块嵌套太深
[3]. C++ tuple(STL tuple)模板用法详解
[4]. Use templight and Templar to debug C++ templates
[5]. 用VC/GCC如何看模板展开后的编译结果?
[6]. 主题:[合集] 用VC/GCC如何看模板展开后的编译结果?
[7]. GCC编译选项—编译模板实例化
[8]. C++ 编译器支持情况表
[9]. 一个C++11实现的轻量级数据库访问库,支持MySQL和SQLite