I'm trying to implement something like static polimorphism using std::variant. I want to declare methods using VARIANT_METHOD or VARIANT_METHOD_CONST, that should take return type, method name, arguments, and qualifiers.
#include <variant>
#define VARIANT_METHOD(retType, name, ...) \
template <typename... Args> retType name (Args&&... args) __VA_ARGS__ { \
return std::visit([...args = std::forward<Args>(args)] <typename T>(T& self) -> retType { \
return self.name(args...); \
}, static_cast<variant&>(*this)); \
}
#define VARIANT_METHOD_CONST(retType, name, ...) template <typename... Args> \
retType name (Args&&... args) __VA_ARGS__ { \
return std::visit([...args = std::forward<Args>(args)]<typename T>(const T& self) -> retType { \
return self.name(args...); \
}, static_cast<const variant&>(*this)); \
}
#define VARIANT_FIELD(name) \
decltype(auto) name() noexcept { \
return std::visit([](auto& self) -> decltype(auto) { \
return self.name; \
}, static_cast<variant&>(*this)); \
} \
decltype(auto) name() const noexcept { \
return std::visit([](const auto& self) -> decltype(auto) { \
return self.name; \
}, static_cast<const variant&>(*this)); \
}
struct A {
int field;
int field2;
int func(int a) const noexcept {
return a + field;
}
int func(int a, int b) const noexcept {
return a * a;
}
};
struct B {
int field2;
int func(int a) const noexcept {
return a * a;
}
int func(int a, int b) const noexcept {
return a * a;
}
};
struct C : protected std::variant<A, B> {
using variant::variant;
VARIANT_FIELD(field2);
VARIANT_METHOD_CONST(int, func, const noexcept); // (1)
};
int main() {
std::vector<C> vec;
vec.emplace_back(A{.field = 0, .field2 = 1});
vec.emplace_back(B{.field2 = 3});
for (auto& c : vec) {
c.func(10);
}
}
I can't declare two methods with same names, but with different arguments. I want to write something like this:
VARIANT_METHOD_CONST(int, func, (int a), const noexcept);
VARIANT_METHOD_CONST(int, func, (int a, int b), const noexcept);
The cleanest way I know of is to abuse operator->*
and make polymorphic method pointers. You can get the same syntax (except ->*
instead of .
), no macros.
template<class F>
struct poly_member {
F f;
friend decltype(auto) operator->*( auto&& t, poly_member const& self) {
return self.f(decltype(t)(t));
}
template<class...Ts>
friend decltype(auto) operator->*( std::variant<Ts...>& var, poly_member const& self ) {
return std::visit( self.f, var );
}
template<class...Ts>
friend decltype(auto) operator->*( std::variant<Ts...>&& var, poly_member const& self ) {
return std::visit( self.f, var );
}
template<class...Ts>
friend decltype(auto) operator->*( std::variant<Ts...> const& var, poly_member const& self ) {
return std::visit( self.f, var );
}
template<class...Ts>
friend decltype(auto) operator->*( std::variant<Ts...> const&& var, poly_member const& self ) {
return std::visit( self.f, var );
}
};
template<class F>
poly_member(F)->poly_member<F>;
template<class F>
struct poly_method {
F f;
auto operator()(auto&&...args)const {
return poly_member{[&](auto&& t)->decltype(auto){
return f( decltype(t)(t), decltype(args)(args)... );
}};
}
friend auto operator->*( auto&& t, poly_method const& self) {
return [&](auto&&...args)->decltype(auto){
return t->*self.f(decltype(args)(args)...);
};
}
friend auto operator->*( auto* t, poly_method const& self) {
return (*t)->*self;
}
};
template<class F>
poly_method(F)->poly_method<F>;
a bit of c++20 alphabet soup. Replace the auto
args with template<class T>
etc for a c++14 version.
You end up making the poly members like this:
constexpr poly_member field { [](auto&& t)->decltype(auto){ return t.field; } };
constexpr poly_member field2 { [](auto&& t)->decltype(auto){ return t.field2; } };
constexpr poly_method func { [](auto&& t, auto&&...args)->decltype(auto){ return t.func(decltype(args)(args)...); } };
and your code looks like:
for (auto& c : vec) {
c->*func(10);
}
now if you don't like writing those constexpr lambdas, you can just make them be written by a macro.
Those poly methods and members work on any instance of any class that supports the lambda stored in them. The difference between members and methods is that methods require a trailing () and pass in those arguments, and members do not.
For example:
A a;
a->*func(3);
acts on an a
just like it does on a variant<A, other stuff>
.
If you are really, really tied to your syntax, I'd advise looking at how google mock does it.
I sometimes extend this and write
template<auto*...methods>
struct poly_any:private std::any {
// code that makes the above alphabet soup look cute
};
and dynamically build vtables for the std::any
I store to access each method
, giving you:
using C = poly_any< &field, &field2, &func >;
C c = A{};
std::cout << c->*field;
std::cout << c->*func(2,3);
where we type erase anything that supports the methods.
But that is my private insanity. (this extension requires you mark up the poly members/methods with partial signature information).