James Moore
James Moore

Reputation: 147

Alternative to macros to aid in type safety and reduced repetition

I am forced to work with a C api that defines a bunch of code very similar to the below

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

It creates a parse function named for every single namespace and type instead of using overloaded functions. It also forces the client code to manage memory.

To aid in this I have wrote some code that looks similar to:

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

The above allows us to have a single parse call that not only manages memory usage for us but is overloaded by type to allow us to use it in a much more c++ oriented way.

The question I have is the following:

Is there a more suitable way to do this without having to resort to using macros? I have full use of C++17 as well as boost. I would rather not have to pollute our codebase with macros unless I am forced to.

Upvotes: 1

Views: 153

Answers (2)

asu
asu

Reputation: 1914

Got it! It seems to work.

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

You only have to specify the parser function for each of those.

I picked up overloaded from somewhere I don't recall, and is usually used for vistor patterns.

The second parameter is a dummy to resolve ambiguity introduced by auto* ptr by forcing the lambda function to only be valid when ptr can be passed to Parser.

I only hope you're not going to try to compile this on MSVC.

Upvotes: 4

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123450

I would rather not have to pollute our codebase with macros unless I am forced to.

You can avoid spreading the usage of macors all over your code by making parse a template and use a trait:

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

And then provide a specialization for each type

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

There might be typos (not tested) but I hope you get the idea.

Not really sure if it is worth the effort, but now you can make this last part in a macro (just replace your REGISTER(NS,TYPE) with a macro that specializes the trait). The user will not notice that there is a macro involved when calling parse.

Upvotes: 1

Related Questions