Reputation: 147
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
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
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