But what if you want to try and access a member, and only use it if it actually exists?
I thought this was simultaneously really neat, and horrible, hence the coding horror section
#define DECLARE_FIND_MEMBER(NAME) \
namespace FindMember { \
template<class T, class R=void> class NAME##_ { \
struct Fallback { int NAME; }; \
struct Derived : T, Fallback { }; \
template<class U, U> struct Check; \
template<class U> static sizeone& Dummy(Check<int Fallback::*, &U::NAME> *); \
template<class U> static sizetwo& Dummy(...); \
template<bool Exists> struct Helper { typedef decltype(&T ::NAME) Type; static Type Pointer() { return &T ::NAME; } }; \
template<> struct Helper<false> { typedef decltype(&Fallback::NAME) Type; static Type Pointer() { return &Fallback::NAME; } }; \
public: \
enum { exists = sizeof(Dummy<Derived>(0)) == 2 }; \
typedef typename Helper<exists>::Type Type; \
static Type Pointer() { return Helper<exists>::Pointer(); } \
}; \
template<class R, class E> \
R* operator->*( E& object, const FindMember::NAME##_<E,R>& ) { \
return internal::TryGetMember<R>(object, FindMember::NAME##_<E>::Pointer()); } \
template<class E, class R> bool NAME(E& entity, R& result) \
{ \
result = internal::TryGetMember<StripPointer<R>::Type>( \
entity, FindMember::NAME##_<E>::Pointer()); \
return FindMember::NAME##_<E>::exists; \
}} //
template<class T, class Y> struct IsSameType { const static bool value = false; };
template<class T > struct IsSameType<T, T> { const static bool value = true; };
template<class T> struct StripRef { typedef T Type; };
template<class T> struct StripRef< T&> { typedef T Type; };
template<class T> struct StripRef<const T&> { typedef T Type; };
template<class T> struct StripPointer { typedef T Type; };//todo - all the combinations...
template<class T> struct StripPointer< T*> { typedef T Type; };
template<class T> struct StripPointer<const T*> { typedef T Type; };
namespace internal {
template<bool condition> struct TryGetMember_
{
template<class Type, class Entity, class MemberPtr>
static Type* GetPointer( Entity& view, MemberPtr ptr )
{
return &((view).*(ptr));
}
};
template<> struct TryGetMember_<false> {
template<class T> static T* GetPointer(...){ return nullptr; }
};
template<class ExpectedType, class T, class MemberPtr>
ExpectedType* TryGetMember( T& entity, MemberPtr ptr )
{
const bool correctType = IsSameType<ExpectedType, T>::value;
return TryGetMember_<correctType>::GetPointer<ExpectedType>( entity, ptr );
}
}
#define TRY_GET_MEMBER( entity, member, type ) \
internal::TryGetMember<type>( entity, FindMember::member##_<StripRef<decltype(entity)>>::Pointer() ) //
#define TRY_GET_MEMBER_ALT( ptr, object, member ) \
((ptr) = internal::TryGetMember<StripPointer<decltype(ptr)>::Type>( \
object, FindMember::member##_<StripRef<decltype(object)>>::Pointer() )) //
Example usage -- I have a scene that contains multiple "DrawItem" structs for each object, which describe how to draw that object in different ways. When doing shadow mapping, or deferred rendering, or forward rendering, the renderer needs to get different sub-sets of these DrawItems.DECLARE_FIND_MEMBER( zpass );
DECLARE_FIND_MEMBER( opaque );
DECLARE_FIND_MEMBER( alpha );
DECLARE_FIND_MEMBER( gbuffer );
class TestModule : public SceneModule<TestModule>
{
public:
template<class SceneView> void Collect( SceneView& view )
{
//Macro syntax
std::vector<DrawItem*>* zpass = TRY_GET_MEMBER( view, zpass, std::vector<DrawItem*> );
std::vector<DrawItem*>* opaque = TRY_GET_MEMBER( view, opaque, std::vector<DrawItem*> );
std::vector<std::pair<float,DrawItem*>>* alpha = TRY_GET_MEMBER( view, alpha, std::vector<std::pair<float,DrawItem*>> );
std::vector<DrawItem*>* gbuffer = TRY_GET_MEMBER( view, gbuffer, std::vector<DrawItem*> );
//Alternate macro syntax:
std::vector<DrawItem*>* zpass = 0;
TRY_GET_MEMBER_ALT( zpass, view, zpass );
//Alternate syntax that doesn't use a macro
std::vector<DrawItem*>* zpass = view->*FindMember::zpass_<SceneView, std::vector<DrawItem*>>();
//Another macroless syntax:
std::vector<DrawItem*>* zpass = 0;
FindMember::zpass(view, zpass);
//n.b. these if statements might be removed at compile-time because in some cases it's provable that they're always testing nullptr
if( opaque )
CollectOpaque( *opaque );
if( alpha )
CollectAlpha( *alpha );
if( zpass )
CollectDepthOnly( *zpass );
if( gbuffer )
CollectGBuffer( *gbuffer );
}
.....
};
struct ShadowMapView
{
std::vector<DrawItem*> zpass;
};
struct ForwardRenderView
{
std::vector<DrawItem*> zpass;
std::vector<DrawItem*> opaque;
std::vector<std::pair<float,DrawItem*>> alpha;
};
struct DeferredRenderView
{
std::vector<DrawItem*> gbuffer;
std::vector<std::pair<float,DrawItem*>> alpha;
};
void test()
{
TestModule m;
DeferredRenderView v;
m.Collect(v); // calls m.CollectGBuffer(v.gbuffer) and m.CollectAlpha(v.alpha)
}
Still debating whether I should make use of this in my engine