C++ duck typing with optional members

Started by
7 comments, last by Hodgman 8 years, 7 months ago
So duck-typing using templates is easy. You have a function that takes a const T& argument, and then you can access it's members by name without knowing anything else about what type of object T actually is.

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 biggrin.png
#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 biggrin.png
Advertisement

My god... it's beautiful and terrifying.

Awesome!!!

[attachment=28865:awesome.png]

can't help being grumpy...

Just need to let some steam out, so my head doesn't explode...

Nice!!!

Some ideas: Put the FindMember classes inside a namespace, e.g. FindMember::Foo instead of FindMember_Foo. Instead of macro TRY_GET_MEMBER, overload operator ->* on these FindMember classes.

std::vector<DrawItem*>* zpass = view->*FindMember::zpass<std::vector<DrawItem*>();

openwar - the real-time tactical war-game platform

Some ideas: Put the FindMember classes inside a namespace, e.g. FindMember::Foo instead of FindMember_Foo. Instead of macro TRY_GET_MEMBER, overload operator ->* on these FindMember classes.


std::vector<DrawItem*>* zpass = view->*FindMember::zpass<std::vector<DrawItem*>();

I like it. Added support for this alternate syntax to the OP and used the namespace idea smile.png

[edit] Also wrote another macroless syntax which is quite clean now!

bool found = FindMember::MemberName(object, outputPointer);

Is there something templates can't do? (make toast?)

Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.


But what if you want to try and access a member, and only use it if it actually exists?

Then you would use Objective C instead.

Shhhh. Objective-C was supposed to die out three decades ago.

But what if you want to try and access a member, and only use it if it actually exists?


Then you would use Objective C instead.

Everyone knows that Objective C is a myth. What's next, unicorns? laugh.png

This topic is closed to new replies.

Advertisement