简体   繁体   English

在编译时或运行时将const char *映射到duck-typed T.

[英]Mapping const char * to duck-typed T at compile-time or run-time

I have many classes A, B, C, D, etc that are duck-typed and thus have the same methods and interface but do not inherit from the same class. 我有许多类型为A,B,C,D等类型的鸭子,因此具有相同的方法和接口,但不从同一个类继承。

Eg 例如

class A {
public:
  void foo();
  void bar();
}
class B {
public:
  void foo();
  void bar();
}
class C {
public:
  void foo();
  void bar();
}

I want to either map const char * to a corresponding instance of one of these classes at run-time eg 我想在运行时将const char *映射到这些类之一的相应实例,例如

"A" -> A a "A" - > A a

"B" -> B b "B" - > B b

Where here a is some instance of class A . 这里的aAA一些实例。

OR map 'const char *` to the the corresponding type at compile-time eg 或者在编译时将'const char *`映射到相应的类型,例如

"A" -> A "A" - > A

I need to use the instance of the object in some other functional call (ie call foo() or bar() ), but the API can only take a const char * as the underlying objects are abstracted away. 我需要在一些其他函数调用中使用对象的实例(即调用foo()bar() ),但API只能采用const char *,因为底层对象被抽象掉了。

I am working in a large, code-genned codebase so changing the paradigm is not practical. 我正在使用代码生成的大型代码库,因此更改范例是不切实际的。

Perform type-erasure using an adaptor interface and a bunch of concrete adaptors implementing that interface; 使用适配器接口和一组实现该接口的具体适配器执行类型擦除; the adaptors can be instances of a class template. 适配器可以是类模板的实例。

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 allows you to trivially solve the compile time version of your problem using compile-time strings, type maps and string lookup structures. Fatal允许您使用编译时字符串,类型映射和字符串查找结构来轻松解决问题的编译时版本。

Let's first start with the headers we'll be using: 让我们首先从我们将要使用的标头开始:

// 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>

In this example, we basically want to associate strings to actions, both represented by types. 在这个例子中,我们基本上想要将字符串与动作相关联,两者都由类型表示。

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"; }
};

Now we create the mapping from compile-time string to an associated type: 现在我们创建从编译时字符串到关联类型的映射:

using my_map = fatal::build_type_map<
  foo_action::name, foo_action,
  bar_action::name, bar_action,
  baz_action::name, baz_action
>;

In order to perform an efficient string lookup at runtime, let's create some lookup structure at compile-time since we already have the strings available to the compiler. 为了在运行时执行有效的字符串查找,让我们在编译时创建一些查找结构,因为我们已经有了编译器可用的字符串。 The actual structure is implementation defined, but it usually uses either a prefix-tree or perfect hashing: 实际结构是实现定义的,但它通常使用前缀树或完美散列:

using my_lookup = my_map::keys::apply<fatal::string_lookup>;

Now, we need a visitor that's going to be called whenever there's a match in the lookup. 现在,我们需要一个访问者,只要在查找中匹配就会调用它。

The visitor will receive the compile-time string as its first parameter, wrapped in a type tag to ensure it's an empty instance. 访问者将接收编译时字符串作为其第一个参数,包装在类型标记中以确保它是一个空实例。

You can receive any number of additional arguments. 您可以接收任意数量的其他参数。 In this example, we're receiving a1 and a2 as extra arguments for demonstration purposes. 在这个例子中,我们接收a1a2作为额外的参数用于演示目的。 They're not mandatory and could safely be removed: 它们不是强制性的,可以安全地删除:

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;
  }
};

All that's left to do now is to look the string up in the lookup structure and call the visitor whenever a match is found. 现在剩下要做的就是在查找结构中查找字符串,并在找到匹配项时调用访问者。

In the code below, we read the runtime string in from the standard input and look it up in the compile-time generated lookup structure. 在下面的代码中,我们从标准输入中读取运行时字符串in并在编译时生成的查找结构中查找它。

As stated above, we're also passing two additional arguments to exact() . 如上所述,我们还将另外两个参数传递给exact() Those arguments are not inspected by exact() , but rather perfectly forwarded to the visitor. 这些参数不是由exact()检查,而是完美地转发给访问者。 They're completely optional and they're here simply to demonstrate how convenient it is to pass additional state to the visitor. 它们是完全可选的,它们只是为了证明将附加状态传递给访问者是多么方便。

In this example, the additional arguments are 56 and "test" : 在这个例子中,附加参数是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;
}

Below is a sample output from this code: 以下是此代码的示例输出:

$ 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
$

The most interesting part about the code above is that in order to support more mappings, all you need to do is add another entry to my_map . 关于上面代码最有趣的部分是,为了支持更多映射,您需要做的就是向my_map添加另一个条目。 The compiler will figure out the rest. 编译器将弄清楚其余部分。

Update : changed code to reflect latest upstream Fatal. 更新 :更改代码以反映最新的上游致命因素。

Note : Since you added the requirement that something is added at runtime, you can't use the solution below. 注意 :由于您添加了在运行时添加某些内容的要求,因此您无法使用下面的解决方案。 I'm also convinced that you are confused about the meaning of duck-typing in C++, as runtime and duck-typing don't work together. 我也相信你对C ++中鸭子打字的含义感到困惑,因为运行时和鸭子打字不能一起工作。


Use a factory template and a bunch of specializations: 使用工厂模板和一堆专业化:

template<int type> void create_ducky();
template<> A create_ducky<'A'>() { return A(); }

and so on and then call it like 等等,然后称之为

create_ducky<'A'>().foo();

However, that's complete nonsense, because instead of writing 'A' , it is much easier to write A as template parameter. 然而,这完全是胡说八道,因为编写A作为模板参数更容易,而不是写'A' I'm relatively sure that what you want isn't such a good idea or that you are at least following a futile path to that (yet unnamed) goal that you really want to reach. 我相对肯定你想要的不是一个好主意,或者你至少在追寻你真正希望达到的那个(尚未命名的)目标。

Perhaps a function returning an instance of boost variant, something like : 也许是一个返回boost变体实例的函数,如:

using V = boost::variant< A, B, C >;
V MyFactory(const char * m) 
{
    if (std::string(m) == "A") return V(A());
    ...
}

You can also instanciate a std::map< std::string, V >. 您还可以实例化std :: map <std :: string,V>。

Boost Fusion and Boost MPL can help: 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" );
}

(running code: http://coliru.stacked-crooked.com/a/aee0daa07510427e ) (运行代码: http//coliru.stacked-crooked.com/a/aee0daa07510427e

Whether this helps or not depends on whether you want runtime polymorphism or compiletime polymorphism (this solution). 这是否有帮助取决于您是否需要运行时多态或编译时多态(此解决方案)。 A wrapper around boost::fusion::map can be made to accept literal chars directly. 可以使boost::fusion::map包装器直接接受文字字符。 Also there is a possibility to use compiletime strings. 还有可能使用编译时字符串。

In your question you say "compiletime or runtime". 在您的问题中,您说“编译时或运行时”。 But, if you need both it is going to be basically "runtime", which means some form of inhertance and pointers (at some level.) For example, std::map<char, BaseofABC*> . 但是,如果你需要它们基本上都是“运行时”,这意味着某种形式的入侵和指针(在某种程度上)。例如, std::map<char, BaseofABC*>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM