1

我被迫使用一个 C api,它定义了一堆与下面非常相似的代码

// Some old obscure c api

// First bunch of class in namespace ns
struct _ns_A { int a; };
struct _ns_AResponse { int resp; };

// Second bunch of classes in another ns
struct _ns2_B { int b; };
struct _ns2_BResponse { int resp; };

// Lots more classes in more namespaces...

// Bunch of parsing classes in namespace ns
_ns_AResponse* parse_A(_ns_A* a)
{
    // do some work 
    return new _ns_AResponse { a->a * 100 }; 
}

// Parsing class in another ns
_ns2_BResponse* parse_B(_ns2_B* b)
{
    // do some work
    return new _ns2_BResponse { b->b * 100 };
}

// And so on....

它为每个命名空间和类型创建了一个解析函数,而不是使用重载函数。它还强制客户端代码管理内存。

为了帮助解决这个问题,我编写了一些类似于以下内容的代码:

// EXAMPLE Expansion: std::unique_ptr<_ns_AResponse> parse(_ns_A* a) { auto ret = parse_A(a); return std::unique_ptr<_ns_AResponse>(ret); }
#define REGISTER_INVOKER(NS, TYPE) inline std::unique_ptr<_##NS##_##TYPE##Response> parse(_##NS##_##TYPE* t) \
 { auto ret = parse_##TYPE(t); return std::unique_ptr<_##NS##_##TYPE##Response>(ret); }

// Register a single parse function for each of our types, stipulating namespace and class
REGISTER_INVOKER(ns, A);
REGISTER_INVOKER(ns2, B);
REGISTER_INVOKER(for 1000s of other types)

int main()
{
    // Invoke our parse method with our unique_ptr to _ns_A
    auto a = std::make_unique<_ns_A>();

    auto a_ret = parse(a.get());

    // And so on...
    auto b = std::make_unique<_ns2_B>();

    auto b_ret = parse(b.get());
}

上面允许我们有一个单独的 parse 调用,它不仅为我们管理内存使用,而且按类型重载,以允许我们以更面向 c++ 的方式使用它。

我的问题如下:

有没有更合适的方法来做到这一点而不必求助于使用宏?我充分利用了 C++17 和 boost。除非我被迫这样做,否则我宁愿不必用宏污染我们的代码库。

4

2 回答 2

4

知道了!它似乎工作。

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

template<auto Parser>
inline auto invoker = [](auto* ptr, decltype(Parser(ptr)) = {})
{
    return std::unique_ptr<std::remove_pointer_t<decltype(Parser(ptr))>>{Parser(ptr)};
};

constexpr auto parse = overloaded {
    invoker<parse_A>,
    invoker<parse_B>
};

int main()
{
    auto a = std::make_unique<_ns_A>();
    auto a_ret = parse(a.get());
    std::cout << a_ret->resp << '\n';

    auto b = std::make_unique<_ns2_B>();
    auto b_ret = parse(b.get());
    std::cout << b_ret->resp << '\n';
}

您只需为其中的每一个指定解析器函数。

我从某个我不记得的地方拾起overloaded,通常用于访问者模式。

第二个参数是一个虚拟参数,用于解决通过强制 lambda 函数仅在可以传递给auto* ptr时才有效而引入的歧义。ptrParser

我只希望你不要试图在 MSVC 上编译它。

于 2018-11-08T22:41:07.133 回答
1

除非我被迫这样做,否则我宁愿不必用宏污染我们的代码库。

parse您可以通过制作模板并使用特征来避免在整个代码中传播宏的使用:

tempalte <typename T>
struct parse_trait;

template <typename T> 
std::unique_ptr<typename parse_trait<T>::response> 
parse( typename parse_trait<T>::type* a) {
    return parse_trait<T>::do_parse(a);
}

然后为每种类型提供一个专业化

template <>
struct parse_trait<_ns_A> {
    using type = _ns_A;
    using response = _ns_AResponse;
    static std::unique_ptr<response> do_parse(type* a) { return parse_A(a); }
};

可能有错别字(未经测试),但我希望你能明白。

不确定是否值得付出努力,但现在您可以在宏中制作最后一部分(只需将您的替换REGISTER(NS,TYPE)为专门针对该特征的宏)。调用时用户不会注意到涉及到宏parse

于 2018-11-08T22:10:09.587 回答