主頁 > 後端開發 > fatal error C1045: 編譯器限制 : 鏈接規范嵌套太深

fatal error C1045: 編譯器限制 : 鏈接規范嵌套太深

2021-01-13 06:15:59 後端開發

前言

我相信你是遇到了同樣的問題、通過搜索引擎來到這里的,為了不耽誤排查問題的時間,我提前說明一下這篇文章所描述的問題范疇:

  • 我遇到的問題和 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++

上一篇:多個java網站一臺服務器怎么部署

下一篇:Grey一個應用開發解決方案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more