Альтернатива макросам для обеспечения безопасности типов и сокращения повторений - PullRequest
0 голосов
/ 09 ноября 2018

Я вынужден работать с API-интерфейсом C, который определяет набор кода, очень похожий на приведенный ниже

// 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());
}

Вышеприведенное позволяет нам иметь один вызов синтаксического анализа, который не только управляет использованием памяти для нас, но и перегружен по типу, что позволяет нам использовать его более ориентированным на c ++ способом.

У меня следующий вопрос:

Есть ли более подходящий способ сделать это, не прибегая к использованию макросов? У меня есть полное использование C ++ 17, а также повышение. Я бы предпочел не загрязнять нашу кодовую базу макросами, если меня не заставят.

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Понял! Вроде работает.

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 откуда-то, что я не помню, и обычно используется для моделей с варисторами.

Второй параметр является фиктивным для разрешения неоднозначности, введенной auto* ptr, заставляя лямбда-функцию быть действительной, только когда ptr может быть передано Parser.

Я только надеюсь, что вы не собираетесь пытаться скомпилировать это в MSVC.

0 голосов
/ 09 ноября 2018

Я бы предпочел не загрязнять нашу кодовую базу макросами, если меня не заставят.

Вы можете избежать распространения использования макросов по всему коду, сделав 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 задействован макрос.

...