我有一個簡單的結構,如:
struct Config {
bool option1;
bool option2;
int arg1;
};
使用 pybind11,我必須匯出成員變數,例如:
py::class_<Config>(m, "Config")
.def_readwrite("option1", &Config::option1)
.def_readwrite("option2", &Config::option2)
.def_readwrite("arg1", &Config::arg1);
當這些結構體很少的時候,寫上面的就可以了。但是當我有大量簡單的結構時,它變得乏味。
是否有一個方便的宏,我可以這樣寫:
PYBIND_EXPORT_STRUCT(Config1);
PYBIND_EXPORT_STRUCT(Config2);
...
每個都掃描并匯出所有給定結構的成員變數?
如果我已經以這種形式撰寫結構會有所幫助嗎:
struct Config {
ADD_PROPERTY(bool, option1);
ADD_PROPERTY(bool, option2);
ADD_PROPERTY(int, arg1);
};
我的問題包括兩部分:
- 要反映一個成員變數回到它的名字的字串。
- 要迭代通過
struct成員。
我知道解決第一部分的內省,typeid(arg1).name()用于檢索名稱字串。
對于第二部分,C 不直接支持。但是,我試圖通過這里的一些答案來弄清楚。
剩下的問題是如何融合上述兩部分以獲得我想象中的PYBIND_EXPORT_STRUCT()功能的作業實作。
也就是說,我不介意以完全不同的表示形式(例如使用宏或元組)來表達我的結構。只要我在使用 pybind11 匯出結構成員時不必再次列舉它們,任何都可以,并且我仍然可以像config1.option1=true在 C 代碼中一樣使用變數。
uj5u.com熱心網友回復:
對于數字 2,您可以嘗試查看我的pod_reflection庫:
// main.cpp
struct Config {
bool option1;
bool option2;
int arg1;
};
#include <pod_reflection/pod_reflection.h>
#include <iostream>
int main()
{
std::cout << "Config size: " << eld::pod_size<Config>() << std::endl;
std::cout << std::boolalpha;
Config conf{true, false, 815};
eld::for_each(conf, [](const auto& i){ std::cout << i << std::endl; });
return 0;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.7.2 FATAL_ERROR)
project(pod_example)
add_subdirectory(pod_reflection)
add_executable(main main.cpp)
target_link_libraries(main eld::pod_reflection)
它可以遍歷 pod 的基本型別元素。還可以通過以下方式使用用戶定義的型別擴展一組型別template<typename ... ArgsT> using extend_feed:
using my_feed = extend_feed<std::string, foo>;
eld::for_each<my_feed>(pod, callableVisitor);
您可能會使用eld::deduced& get<I, TupleFeed>(POD& pod)來填充py::class_<Config>. 但是,由于庫不可能知道 pod 成員的名稱,因此您必須想辦法從I. 如果沒有適當的編譯時反射,幾乎不可能實作自動化。請注意,get用于reinterpret_cast通過偏移量獲取指向成員的指標。
uj5u.com熱心網友回復:
1.如何不解決問題
您想到的方法都不可行也不實用。
我知道解決第一部分的內省,
typeid(arg1).name()用于檢索名稱字串。
這是不正確的。C 有 RTTI,即運行時型別資訊,但與 C#、Java 或 Python 所具有的“反射”相去甚遠。特別是,成員函式std::type_info::name()“回傳一個實作定義的包含型別名稱的空終止字串。不提供任何保證;特別是,回傳的字串對于多種型別可以是相同的,并且在同一程式的呼叫之間會發生變化。” 【亮點是我的-kkm】其實這個程式
#include <iostream>
#include <typeinfo>
struct Config { int option; };
int main() { std::cout << typeid(&Config::option).name() << "\n"; }
列印,如果在 Linux x64 上使用 GCC 11 編譯,
M6Configi
這是完全符合標準的。這里是你的第 1 部分。一類不包含成員的名字,它被稱為運行時型別,情報,而不是運行時名稱資訊的一個原因。您甚至可以進行有根據的猜測并解碼列印的字串:M= 指向成員的指標,6= 接下來的 6 個字符命名結構型別,Config= 明顯,i= int。指向 type 成員的指標Config,型別int本身。但是另一個編譯器會以不同的方式對型別進行編碼(“mangle”,如何稱呼)。
關于第 2 部分,請觀看此CppCon 視頻演示(來自您正在鏈接的答案)的真實情況:它證明了 C 14 元編程功能強大到足以提取有關 POD 型別的資訊。如您所見,presenter 為您可能遇到的每個成員型別宣告了兩個函式(int, volatile int, const int, const volatile int, short, ...)。讓我們就此打住吧。所有這些型別都是不同的。事實上,當我volatile int option;在上面的小測驗程式中將單獨結構成員的宣告更改為時,它列印了一個不同的重整型別名稱:M6ConfigVi.
CppCon 演示文稿展示了機器的功能,而不是它應該用于什么。打個比方,這就是飛機在航展上的滾桶對常規客運航空公司運營的影響。如果我是你,我會避免在生產代碼中使用桶滾……
實際上,這對編譯器來說是一個很好的測驗。我過去常常使用更溫和的元編程結構導致編譯器崩潰。此外,您可能不會喜歡所有這些 kaboodle 的編譯時間。坐等 10 分鐘直到編譯器完成編譯,請不要感到驚訝,四種方式之一:崩潰;內部錯誤報告;成功生成錯誤代碼;或者,手指交叉,成功生成正確的代碼。另外,你需要一個深,我的意思是真的,真的很深理解元編程,編譯器如何選擇不同的模板多載,什么是未評估的背景關系和 SFINAE,等等。簡單地說,就是不要。它可能作業,但不值得讓會議演示作業所需的大量“框架”代碼以及編譯器正確性的不確定性 wrt 這樣一個極其復雜的元程式。
2.如何解決問題
有一種非常傳統的方式來做你想做的事情,依賴于普通的舊 C 前處理器宏。核心思想是這樣的:您將結構的定義作為類函式前處理器宏撰寫在一個單獨的檔案中,該檔案不包含這些宏的定義(我們稱其為“抽象定義檔案”或 ADF,對于需要一個公認的術語)。第二個檔案,您包含的普通頭檔案,用于獲取結構的具體宣告,定義這些特殊的宏以擴展為普通的 C 構造,然后包含 ADF,然后(重要!)對#undef它們進行處理。創建 Python 系結的第三個檔案首先包含頭檔案,然后定義相同但不同的宏(這就是為什么#undefs 很重要!),這一次它們擴展到 pybind11 語法結構;然后在同一編譯單元中第二次包含 ADF 。現在讓我們把整個事情放在一起。
第一個檔案是自動進稿器,structs.absdef。我不會給它傳統的.h擴展名,以防止它與“普通”頭檔案混淆。擴展可以是你想要的任何東西,但在專案中選擇一個獨一無二的會很有幫助。
/* structs.absdef -- abstract definition of data structures */
#ifndef BEGIN_STRUCT_DEF
#error "This file should be included only from structs.h or pybind.cc"
#endif
BEGIN_STRUCT_DEF(Config)
STRUCT_MEMBER(Config, bool, option1)
STRUCT_MEMBER(Config, bool, option2)
STRUCT_MEMBER(Config, int, arg1)
END_STRUCT_DEF()
/* ... and then structs, structs and more structs ... */
該#ifndef/ #error/#endif只是為了立即如果前處理器宏不包括檔案之前定義停止匯編; 否則,你會得到一堆編譯錯誤,更可能是誤導而不是對診斷問題有幫助。
This file will be included into the second file, which is your normal C header which defines all the structs in C syntax. This is the file that you include as a normal, plain and boring C header into your C source and/or other include files, where you want the declaration of these structs structs be visible.
/* structs.h -- C concrete definitions of data structures */
#ifndef MYPROJECT_STRUCTS__H
#define MYPROJECT_STRUCTS__H
#define BEGIN_STRUCT_DEF(stype) struct stype {
#define STRUCT_MEMBER(stype, mtype, name) mtype name;
#define END_STRUCT_DEF() };
#include "structs.absdef"
#undef BEGIN_STRUCT_DEF
#undef STRUCT_MEMBER
#undef END_STRUCT_DEF
#endif // MYPROJECT_STRUCTS__H
One thing to note here is that this file has include guards but the ADT doesn't. This is so because it is included twice in the compilation unit with the pybind calls. This C file is special: it transforms the same ADT definitions into pybind syntax. I have no idea how pybind works; I'm blindly copying your example.
/* pybind.cc -- Generate pybind11 Python bindings */
#include "pybind11.h" // All these #include ...
#include "other.h" // ... directives stand ...
#include "stuff.h" // ... for the real McCoy.
#include "structs.h" /* You need "normal" C definitions, too! */
// We rely here on the ADF having had #undef'd it's definition of these.
// The preprocessor does not allow silently redefining macros.
#define BEGIN_STRUCT_DEF(stype) py::class_<stype>(m, #stype)
#define STRUCT_MEMBER(stype, mtype, name) .def_readwrite(#name, &stype::name)
#define END_STRUCT_DEF() ;
void create_pybind_bindings() {
// The ADF is included the second time in the CU.
#include "structs.absdef"
}
// Not necessary, but customary to avoid polluting the preprocessor
// namespace, unless the C source ends right here.
#undef BEGIN_STRUCT_DEF
#undef STRUCT_MEMBER
#undef END_STRUCT_DEF
Two points that you should pay attention to.
First, there is no space between function-like macro and the opening parenthesis:
// Correct:
#define FOO(x) ((x) 42)
// This
int j = FOO(1);
// `FOO(1)' expands into by replacing `x' with `1':
int j = ((1) 42);
// Incorrect:
// v--- A feral space attacks!!! Everyone seek shelter!!!
#define BAR (x) ((x) 42)
// Since BAR is not a function-like macro, it expands literally
// as defined into `(x) ((x) 42)', such that this:
int j = BAR(1);
// expands into:
int j = (x) ((x) 42)(1);
i.e. BAR is substituted literally and exactly where it appears. What your compiler will have to say when it tries to digest the result is a load of garbage errors, and certainly not "error: you inserted a space between BAR and (", so be careful.
Second point is the use of the preprocessor's stringizing operator #, which expands the following function-like macro argument into a double-quoted string: #sname turns into "Config", in quotes, which is just what you need to pass to the pybind API.
3. Bonus: a peek under the hood
Obviously, we don't have the files "pybind11.h", "other.h" and "stuff.h": they are just placeholder names, so I'll simply create empty ones. The 3 other files I have literally copied from this answer. When you compile pybind.cc, the C preprocessor is first invoked by the compiler driver. We'll invoke it alone and examine its output. The c -E <filename.cc> command tells the compiler to call the preprocessor, but instead of ingesting the resulting file, just print it to stdout and stop.
我通過壓縮多個空行來壓縮輸出:前處理器去除注釋行和帶有它接受和處理的指令的行,但仍然列印空行以維護正確的診斷行號,可能由下一個處理階段輸出。以 開頭的額外行#也用于下一次傳遞和相同的目的:它們只是確定正在處理的行號和檔案名。忽略它們是很好的衡量標準。
$ touch "pybind11.h" "other.h" "stuff.h"
$ ls *.{cc,h,absdef}
other.h pybind.cc pybind11.h structs.absdef structs.h stuff.h
$ c -E pybind.cc
# 1 "pybind.cc"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "pybind.cc"
# 1 "pybind11.h" 1
# 4 "pybind.cc" 2
# 1 "other.h" 1
# 5 "pybind.cc" 2
# 1 "stuff.h" 1
# 6 "pybind.cc" 2
# 1 "structs.h" 1
# 10 "structs.h"
# 1 "structs.absdef" 1
struct Config {
bool option1;
bool option2;
int arg1;
};
# 11 "structs.h" 2
# 8 "pybind.cc" 2
void create_pybind_bindings() {
# 1 "structs.absdef" 1
py::class_<Config>(m, "Config")
.def_readwrite("option1", &Config::option1)
.def_readwrite("option2", &Config::option2)
.def_readwrite("arg1", &Config::arg1)
;
# 15 "pybind.cc" 2
}
或者,沒有# number file flags僅編譯器列印正確診斷背景關系所需的表單提示(如“structs.absdef:5 包含從 structs.h:10: error: ...”),漂亮干凈的精確副本實際編譯器處理的編譯單元所需的代碼是:
struct Config {
bool option1;
bool option2;
int arg1;
};
void create_pybind_bindings() {
py::class_<Config>(m, "Config")
.def_readwrite("option1", &Config::option1)
.def_readwrite("option2", &Config::option2)
.def_readwrite("arg1", &Config::arg1)
;
}
4. Colophon,或一點聰明和一點歷史
- 并非每一項新技術都適用于一切事物,僅僅因為它是新的。
- 前處理器實際上比 C 語言本身稍早。 49歲,準確的說。 C 采用了貝爾實驗室內部用于其他語言的前處理器。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/386443.html
上一篇:對sizeof的無效寫入
