[英]Mapping const char * to duck-typed T at compile-time or run-time
我有許多類型為A,B,C,D等類型的鴨子,因此具有相同的方法和接口,但不從同一個類繼承。
例如
class A {
public:
void foo();
void bar();
}
class B {
public:
void foo();
void bar();
}
class C {
public:
void foo();
void bar();
}
我想在運行時將const char *
映射到這些類之一的相應實例,例如
"A"
- > A a
"B"
- > B b
這里的a
是A
類A
一些實例。
或者在編譯時將'const char *`映射到相應的類型,例如
"A"
- > A
我需要在一些其他函數調用中使用對象的實例(即調用foo()
或bar()
),但API只能采用const char *,因為底層對象被抽象掉了。
我正在使用代碼生成的大型代碼庫,因此更改范例是不切實際的。
使用適配器接口和一組實現該接口的具體適配器執行類型擦除; 適配器可以是類模板的實例。
struct IFooBar {
virtual ~IFooBar() {}
virtual void foo() = 0;
virtual void bar() = 0;
};
template<class T> struct FooBarAdaptor : IFooBar {
T* t;
FooBarAdaptor(T* t) : t{t} {} ~FooBarAdaptor() {}
void foo() override { return t->foo(); }
void bar() override { return t->bar(); }
};
// ...
A a;
B b;
C c;
std::map<std::string, std::unique_ptr<IFooBar>> m;
m["a"] = std::make_unique<FooBarAdaptor<A>>(&a);
m["b"] = std::make_unique<FooBarAdaptor<B>>(&b);
m["c"] = std::make_unique<FooBarAdaptor<C>>(&c);
Fatal允許您使用編譯時字符串,類型映射和字符串查找結構來輕松解決問題的編譯時版本。
讓我們首先從我們將要使用的標頭開始:
// type_map so we can associated one type to another
#include <fatal/type/map.h>
// for efficient compile-time built string lookup structures
#include <fatal/type/string_lookup.h>
// for compile-time string
#include <fatal/type/sequence.h>
在這個例子中,我們基本上想要將字符串與動作相關聯,兩者都由類型表示。
struct foo_action {
// FATAL_STR creates a compile-time string, equivalent to
// `using name = fatal::constant_sequence<char, 'f', 'o', 'o'>;`
FATAL_STR(name, "foo");
static void DOIT() { std::cout << "FOO-ACTION"; }
};
struct bar_action {
FATAL_STR(name, "bar");
static void DOIT() { std::cout << "BAR-ACTION"; }
};
struct baz_action {
FATAL_STR(name, "baz");
static void DOIT() { std::cout << "BAZ-ACTION"; }
};
現在我們創建從編譯時字符串到關聯類型的映射:
using my_map = fatal::build_type_map<
foo_action::name, foo_action,
bar_action::name, bar_action,
baz_action::name, baz_action
>;
為了在運行時執行有效的字符串查找,讓我們在編譯時創建一些查找結構,因為我們已經有了編譯器可用的字符串。 實際結構是實現定義的,但它通常使用前綴樹或完美散列:
using my_lookup = my_map::keys::apply<fatal::string_lookup>;
現在,我們需要一個訪問者,只要在查找中匹配就會調用它。
訪問者將接收編譯時字符串作為其第一個參數,包裝在類型標記中以確保它是一個空實例。
您可以接收任意數量的其他參數。 在這個例子中,我們接收a1
和a2
作為額外的參數用於演示目的。 它們不是強制性的,可以安全地刪除:
struct lookup_visitor {
// note that we don't give the type_tag parameter a name
// since we're only interested in the types themselves
template <typename Key>
void operator ()(fatal::type_tag<Key>, int a1, std::string const &a2) const {
// Key is the compile-time string that matched
// now let's lookup the associated type in the map:
using type = typename my_map::template get<Key>;
// at this point you have `type`, which is the type associated
// to `Key` in `my_map`
// TODO: at this point you can do whatever you like with the mapped type
// and the extra arguments. Here we simply print stuff and call a method from
// the mapped type:
std::cout << "performing action from type '" << typeid(type).name()
<< "' (additional args from the call to exact: a1="
<< a1 << ", a2='" << a2 << "'):";
// call the static method `DOIT` from the mapped type
type::DOIT();
std::cout << std::endl;
}
};
現在剩下要做的就是在查找結構中查找字符串,並在找到匹配項時調用訪問者。
在下面的代碼中,我們從標准輸入中讀取運行時字符串in
並在編譯時生成的查找結構中查找它。
如上所述,我們還將另外兩個參數傳遞給exact()
。 這些參數不是由exact()
檢查,而是完美地轉發給訪問者。 它們是完全可選的,它們只是為了證明將附加狀態傳遞給訪問者是多么方便。
在這個例子中,附加參數是56
和"test"
:
int main() {
for (std::string in; std::cout << "lookup: ", std::cin >> in; ) {
// exact() calls the visitor and returns true if a match is found
// when there's no match, the visitor is not called and false is returned
bool const found = my_lookup::match<>::exact(
in.begin(), in.end(), lookup_visitor(),
56, "test"
);
if (!found) {
std::cout << "no match was found for string '" << in << '\''
<< std::endl;
}
}
return 0;
}
以下是此代碼的示例輸出:
$ clang++ -Wall -std=c++11 -I path/to/fatal sample.cpp -o sample && ./sample
lookup: somestring
no match was found for string 'somestring'
lookup: test
no match was found for string 'test'
lookup: foo
performing action from type '10foo_action' (additional args from the call to exact: a1=56, a2='test'): FOO-ACTION
lookup: bar
performing action from type '10bar_action' (additional args from the call to exact: a1=56, a2='test'): BAR-ACTION
lookup: ^D
$
關於上面代碼最有趣的部分是,為了支持更多映射,您需要做的就是向my_map
添加另一個條目。 編譯器將弄清楚其余部分。
更新 :更改代碼以反映最新的上游致命因素。
注意 :由於您添加了在運行時添加某些內容的要求,因此您無法使用下面的解決方案。 我也相信你對C ++中鴨子打字的含義感到困惑,因為運行時和鴨子打字不能一起工作。
使用工廠模板和一堆專業化:
template<int type> void create_ducky();
template<> A create_ducky<'A'>() { return A(); }
等等,然后稱之為
create_ducky<'A'>().foo();
然而,這完全是胡說八道,因為編寫A
作為模板參數更容易,而不是寫'A'
。 我相對肯定你想要的不是一個好主意,或者你至少在追尋你真正希望達到的那個(尚未命名的)目標。
也許是一個返回boost變體實例的函數,如:
using V = boost::variant< A, B, C >;
V MyFactory(const char * m)
{
if (std::string(m) == "A") return V(A());
...
}
您還可以實例化std :: map <std :: string,V>。
Boost Fusion和Boost MPL可以幫助:
#include<boost/fusion/container/generation/make_map.hpp>
#include<boost/mpl/char.hpp>
#include<cassert>
#include<boost/fusion/container/generation/make_map.hpp>
#include <boost/fusion/sequence/intrinsic/at_key.hpp>
#include<boost/mpl/char.hpp>
#include<cassert>
struct A{std::string call(){return "a";}};
struct B{std::string call(){return "b";}};
struct C{std::string call(){return "c";}};
int main(){
namespace fus = boost::fusion;
namespace mpl = boost::mpl;
A a{};
B b{};
C c{};
auto mymap = fus::make_map<
mpl::char_<'a'>,
mpl::char_<'b'>,
mpl::char_<'c'>
>(
a, // or use
b,
c
);
// which is of type
// fus::map<
// fus::pair<mpl::char_<'a'>, A>,
// fus::pair<mpl::char_<'b'>, B>,
// fus::pair<mpl::char_<'c'>, char>
// >
// and it is used as:
assert( fus::at_key<mpl::char_<'b'>>(mymap).call() == "b" );
}
(運行代碼: http : //coliru.stacked-crooked.com/a/aee0daa07510427e )
這是否有幫助取決於您是否需要運行時多態或編譯時多態(此解決方案)。 可以使boost::fusion::map
包裝器直接接受文字字符。 還有可能使用編譯時字符串。
在您的問題中,您說“編譯時或運行時”。 但是,如果你需要它們基本上都是“運行時”,這意味着某種形式的入侵和指針(在某種程度上)。例如, std::map<char, BaseofABC*>
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.