前言
我相信你是遇到了同樣的問題、通過搜索引擎來到這里的,為了不耽誤排查問題的時間,我提前說明一下這篇文章所描述的問題范疇:
- 我遇到的問題和 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 = https://www.cnblogs.com/goodcitizen/p/%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

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/247915.html
標籤:C++
下一篇:Grey一個應用開發解決方案
