前言

我相信你是遇到了同样的问题、通过搜索引擎来到这里的。为了不耽误排查问题的时间,我提前说明一下这篇文章所描述的问题范畴:

  • 我遇到的问题和 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

fatal error C1045: 编译器限制 : 链接规范嵌套太深的更多相关文章

  1. fatal error C1061: 编译器限制 : 块嵌套太深

    VisualStudio开发过程中碰到C1061报错,查了MSDN,文档说明如下 从说明中我们明白这是由于我们的代码块嵌套太深,超过了编译器的限制.但我理解为应该是同一个域内块的数量太多,超过了编译器 ...

  2. fatal error c1001 编译器中发生内部错误 OpenMesh6.3

    Internal Compiler Error VS 2015 Update1 VS2015 Update1 编译OpenMesh的额代码时发生错误 fatal error c1001 编译器中发生内 ...

  3. 【VS开发】fatal error C1001:编译器中发生内部错误

    自己编译boost的库文件时遇到这个错误的,大概报错情况如下:  mp_defer.hpp<50>:fatal error C1001:编译器中发生内部错误.  1> 要解决此问题, ...

  4. Avoid nesting too deeply and return early避免嵌套太深应尽早返回

    当某个变量与多个值进行比较的时候 不要用多个if else 判断是否相等 将多个值放在数组里,然后用PHP函数in_array(mixed $needle,array $haystack),检查数组$ ...

  5. VS中c++文件调用c 函数 ,fatal error C1853 预编译头文件来自编译器的早期版本号,或者预编译头为 C++ 而在 C 中使用它(或相反)

    出现错误:error C1853: "Debug\ConsoleApplication1.pch"预编译头文件来自编译器的早期版本号.或者预编译头为 C++ 而在 C 中使用它(或 ...

  6. fatal error C1047: 对象或库文件“.\x64\Release\Des.obj”是使用比创建其他对象所用编译器旧的编译器创建的;请重新生成旧的对象和库

    问题描述: 在把一个32位的dll编译成64位的时候提示上面的错误 解决办法: >属性->常规->项目默认值->全程序优化  将这里的默认项 "使用链接时间代码生成& ...

  7. C1853 编译器错误:fatal error C1853: &#39;pjtname.pch&#39; precompiled header file is from a previous

    转载:https://www.cnblogs.com/emanlee/archive/2010/10/16/1852998.html 用VC++ 2008 编写C语言程序,编译出现错误: 预编译头文件 ...

  8. VC++ 链接错误LINK : fatal error LNK1104: cannot open file &quot;*.lib&quot;

    问题描述: 运行VC++编译时经常出现 Linking… LINK : fatal error LNK1104: cannot open file “*.lib” Error executing li ...

  9. 【VS开发】fatal error C1853: "Debug\sift.pch"预编译头文件来自编译器的早期版本

    fatal error C1853: "Debug\sift.pch"预编译头文件来自编译器的早期版本 <pre id="best-content-12991040 ...

  10. LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏

    同时安装了VS2012和VS2010,用VS2010 时 >LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏 问题说明:当安装VS2012之后 ...

随机推荐

  1. IOS UIAppLocation 单例模式

    UIApplocation * app=[UIApplocation shareapplocation]; UIAppLocation 只能被初始化一次. 一个程序中只能被创建一次,称为单例模式. 单 ...

  2. 由于客户端检测到一个协议错误 代码0x1104

    重新连接N次都还是这个错误提示,最后再重起电脑,还是没用.研究了一下错误终于解决了. 首先检查远程连接端口对不对?Windows远程默认的连接端口是3389,一般大家连接时直接输入IP或域名就可以连接 ...

  3. Java文件选择对话框(文件选择器JFileChooser)的使用:以一个文件加密器为例

    文件加密器,操作过程肯定涉及到文件选择器的使用,所以这里以文件加密器为例.下例为我自己写的一个文件加密器,没什么特别的加密算法,只为演示文件选择器JFileChooser的使用. 加密器界面如图: 项 ...

  4. java 实现从15位~18位的身份证号码转换,校验中国大陆公民身份证、香港居民身份证、澳门身份证和台湾身份证。

    package xidian.sl.netcredit.util; /** * Copyright (C) 2009-2010 Yichuan, Fuchun All rights reserved. ...

  5. bzoj1016 [JSOI2008]最小生成树计数

    1016: [JSOI2008]最小生成树计数 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 3517  Solved: 1396[Submit][St ...

  6. C#读写txt文件的两种方法介绍

    C#读写txt文件的两种方法介绍 1.添加命名空间 System.IO; System.Text; 2.文件的读取 (1).使用FileStream类进行文件的读取,并将它转换成char数组,然后输出 ...

  7. trackr: An AngularJS app with a Java 8 backend – Part I

    该系列文章来自techdev 我想分享在techdev公司开发的项目-trackr-的一些最新的见解.trackr是一个用来跟踪我们的工作时间,创建报告和管理请假的web应用程序.做这个程序的目的有两 ...

  8. EDM(邮件营销)

    官冲拉手网CTO举了个EDM(邮件营销)的例子: 在做大数据分析应用之前,通过EDM带来的订单转化率很低.在采用大数据解决方案后,可以根据用户之前的浏览习惯猜测他的喜好和购买习惯,从而针对性的推送个性 ...

  9. CSS 命名规则

    CSS书写顺序: 位置属性(position, top, right, z-index,display, float等) 大小(width, height, padding, margin) 文字系列 ...

  10. centos6.2+nginx-1.2.3+php-5.3.17安装脚本

    #!/bin/bash # # vm test install script # # create by xk # # data 2013-04-25 # # SOFTPATH=/home/tools ...