First off - I don't think most of what I'm asking below is possible at this point in the language. Then again, the pockets of trickery run deep when it comes to crafty programmers, so I'm hoping to be proven wrong.
Secondly, I only have one target: I want this to work on the latest version of Visual Studio with std:c++latest enabled. I don't really care if it's compatible with GCC or anything else.
Thirdly, there are actually several questions below, each of which may or may not be possible to a different degree.
Here's a short code snippet as you might see it in practice:
class IInterface {
protected:
// implement the getter via void*
virtual
void* _Impl_GetIt(bool, bool, bool) = 0;
public:
// provide a non-const getter interface
template<typename T = void>
T* GetIt(bool a, bool b, bool c) { return static_cast<T*>(_Impl_GetIt(a, b, c)); }
// forward const version to non-const; could call _Impl_GetIt() directly here,
// but in the long run this seems more error prone to me. But that's neither
// here nor there
template<typename T = void>
const T* GetIt(bool a, bool b, bool c) const { const_cast<IInterface*>(this)->GetIt(a, b, c); }
};
class Implementation {
public:
void* GetIt(bool a, bool b, bool c) { return it; }
};
class Proxy
: public IInterface, public Implementation {
public:
// our interface and implementation are not evertically related, so need to
// use a forwarding proxy call to have them communicate
void* _Impl_GetIt(bool a, bool b, bool c) {
return Implementation::GetIt(a, b, c);
}
};
As you can see - implementing basic const/non-const narrowing getters with a forwarding call bewteen a pure virtual interface and implementation requires a fantastic amount of boilerplate code (if you're asking why I'm not deriving Implementation from IInterface then that's a whole different topic; please accept for now that this is intended).
My objective is to ideally reduce the above code to something like this:
class IInterface
{
DECL_PureBaseGetter(void, GetIt, bool, bool, bool);
};
class Implementation
{
public:
void* GetIt(bool a, bool b, bool c) { return it; }
};
class Proxy
: public IInterface, public Implementation
{
public:
FORWARD_ToBase(GetIt, bool, bool, bool);
};
Now, I'm actually kinda close to achieving this, but there are three primary problems I don't know how to deal with. Here's what my solution looks thus far (Implementation omitted as it looks the same):
class IInterface
{
DECL_TemplatedPureBaseGetter3(void, GetIt, bool, bool, bool);
};
class Proxy
: public IInterface, public Implementation
{
public:
using _BaseClass = Implementation;
FORWARD_ToBaseTemplated(GetIt, bool, bool, bool);
};
Partial code for the macros in question looks like this (I'm excluding the for-each expander since it's kinda lengthy, although I can post it below since it could maybe be modified to deal with at least one of the problems (number 3)):
#define __expand_param_to_braces2(T, o, comma, index) { * ((::std::remove_reference_t<o>*)(0))} comma
#define __EXPAND_ARGS_TO_BRACES_IC(...) DO_FOR_EACH_INDEXED_COMMA(__expand_param_to_braces2, 0, __VA_ARGS__)
#define __expand_param_to_named2(T, o, comma, index) o _arg##index comma
#define __EXPAND_ARGS_TO_NAMED_IC(...) DO_FOR_EACH_INDEXED_COMMA(__expand_param_to_named2, 0, __VA_ARGS__)
#define __expand_param_to_forward2(T, o, comma, index) (o)_arg##index comma
#define __EXPAND_ARGS_TO_FORWARD_IC(...) DO_FOR_EACH_INDEXED_COMMA(__expand_param_to_forward2, 0, __VA_ARGS__)
#define DECL_TemplatedPureBaseGetter3(_DefaultReturn, _Name, _Arg1, _Arg2, _Arg3) \
protected: \
_PureBasemethod \
_DefaultReturn* _Impl_##_Name(_Arg1, _Arg2, _Arg3) = 0; \
public: \
template<typename Type = _DefaultReturn> \
Type* _Name(_Arg1 a, _Arg2 b, _Arg3 c) { return static_cast<Type*>(_Impl_##_Name(a, b, c)); } \
template<typename Type = _DefaultReturn> \
const Type* _Name(_Arg1 a, _Arg2 b, _Arg3 c) const { return const_cast<_ThisType*>(this)->_Name<Type>(); }
#define __FORWARD_Impl(_BaseClass, FunctionName, ProxyName, ...) \
decltype(::std::declval<_BaseClass&>().FunctionName(__EXPAND_ARGS_TO_BRACES_IC(__VA_ARGS__))) \
ProxyName(__EXPAND_ARGS_TO_NAMED_IC(__VA_ARGS__)) _Override { \
return _BaseClass::FunctionName(__EXPAND_ARGS_TO_FORWARD_IC(__VA_ARGS__)); }
////// HELPER MACROS BELOW ///////
#define FORWARD_ToBase(FunctionName, ...) __FORWARD_Impl(_BaseClass, FunctionName, FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseEx(_BaseClass, FunctionName, ...) __FORWARD_Impl(_BaseClass, FunctionName, FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseTemplated(FunctionName, ...) __FORWARD_Impl(_BaseClass, FunctionName, _Impl_##FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseTemplatedEx(_BaseClass, FunctionName, ...) __FORWARD_Impl(_BaseClass, FunctionName, _Impl_##FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseConst(FunctionName, ...) __FORWARD_ImplConst(_BaseClass, FunctionName, FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseConstEx(_BaseClass, FunctionName, ...) __FORWARD_ImplConst(_BaseClass, FunctionName, FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseConstTemplated(FunctionName, ...) __FORWARD_ImplConst(_BaseClass, FunctionName, _Impl_##FunctionName, __VA_ARGS__)
#define FORWARD_ToBaseConstTemplatedEx(_BaseClass, FunctionName, ..) __FORWARD_ImplConst(_BaseClass, FunctionName, _Impl_##FunctionName, __VA_ARGS__)
#define FORWARD_ToBase0(FunctionName) __FORWARD_Impl0(_BaseClass, FunctionName, FunctionName)
#define FORWARD_ToBase0Ex(_BaseClass, FunctionName) __FORWARD_Impl0(_BaseClass, FunctionName, FunctionName)
#define FORWARD_ToBase0Templated(FunctionName) __FORWARD_Impl0(_BaseClass, FunctionName, _Impl_##FunctionName)
#define FORWARD_ToBase0TemplatedEx(_BaseClass, FunctionName) __FORWARD_Impl0(_BaseClass, FunctionName, _Impl_##FunctionName)
#define FORWARD_ToBase0Const(FunctionName) __FORWARD_Impl0Const(_BaseClass, FunctionName, FunctionName)
#define FORWARD_ToBase0ExConst(_BaseClass, FunctionName) __FORWARD_Impl0Const(_BaseClass, FunctionName, FunctionName)
#define FORWARD_ToBase0TemplatedConst(FunctionName) __FORWARD_Impl0Const(_BaseClass, FunctionName, _Impl_##FunctionName)
#define FORWARD_ToBase0TemplatedExConst(_BaseClass, FunctionName) __FORWARD_Impl0Const(_BaseClass, FunctionName, _Impl_##FunctionName)
I wrote a small explanation of what all of this does below.
If you look to the last code block, you'll see the line ////// HELPER MACROS BELOW ///////
. All of the succeeding macros need to be used for various types of functions.
The problems I'm trying to solve are:
- The first issue has to do with const and non-const functions because I don't know how to selectively emit the trailing const modifier for the proxy call. This basically means that
__FORWARD_Impl
has an almost identical__FORWARD_ImplConst
version, which only adds the const modifier to the end of the forwarding call. - I specifically used a templated getter in this example as it exemplifies another problem: the need to implement a general getter underneath the templated versions that doesn't share a signature with the templated ones. This name needs to be known by both the interface and proxy classes, which means I need to draw a distinction between a macro written for a straight up simple forwarding function and one that needs to know that the top level functions were templated. In my case I'm simply automatically tacking _Impl_ in front of the (hidden) proxy getter.
- I can deal with the expansion of multiple arguments and auto-naming them. However, the for-each macro can't emit empty fields, which means that the expansion of the declaration needs a special case if __VA_ARGS__ is empty. Again, I purposefully used an example with multiple arguments since it shows how I need a
DECL_TemplatedPureBaseGetter3
macro specifically for functions with 3 arguments. You can see where this is going. Ideally, a single macro would be able to deal with any number of arguments, including zero, but I feel like this either requires the for-each expansion to be somehow differently written or I need to be able to generalize an if statement into it, which I don't think can realistically be done.
I have a feeling something like noexcept
might help with dealing with issue 2. I have no idea how to go about 1 and 3 might somehow be doable, but I'm inclined to think the answer's still no.
Some notes on the code:
Since the code can seem a bit opaque, I'll highlight some of the more relevant bits:
- I'm using
using _BaseClass = Implementation;
because I need to know the actual name of the implementation class (this becomes relevant when Proxy has more than one base class). - The main trickery takes place in the
__FORWARD_Impl()
and__EXPAND_ARGS_TO_BRACES_IC()
macros. The idea is to retrieve the return type of the function in the base (Implementation) class by constructing the function prototype from the arguments provided by the user. HenceFORWARD_ToBaseTemplated(GetIt, bool, bool, bool);
can use the 3 bool arguments to find the appropriateGetIt
function inImplementation
and automatically deduce the return type to from it (this works on the same premise that you can't have multiple functions with the same signature that only differ by return type). I could also get the constness of the called function, but I don't know how to use this to automatically emit the trailingconst
modifier within the macro itself. - In order to construct the signature and retrieve the return type from be base I use the following meta-implementation:
decltype(::std::declval<_BaseClass&>().FunctionName({} {} {})
. However, in order to deal with variants I'm using further trickery to specialize the 3 brace pairs properly. And I mean trickery, because using raw types won't work as soon as you have to null-initialize an argument that's a non-const reference. In particular, I'm creating null pointer versions of each argument and then dereferencing them to construct the virtual arguments: see{ * ((::std::remove_reference_t<o>*)(0))}
where o is the type name of the argument. - The rest is kinda technical and verbose but not all that complicated: e.g. I expand the provided parameters to bool arg0, bool arg1 and bool arg2 in the proxy call declaration, use the same names in the forwarding call, and so forth.