🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Clever function call? (c++)

Started by
4 comments, last by Juliean 4 years, 4 months ago

Hi!

Any way to write a function in C++ so i could replace the below:

 for (int i = 0; i < MAX_BLOCK; i++)
 for (int ii = 0; ii < MAX_BLOCK; ii++)
  for (int iii = 0; iii < BUILDING_X; iii++)
   for (int iiii = 0; iiii < BUILDING_Y; iiii++){
      block[i][ii].building[iii][iiii].doSomething();
}

with something like this (i access every building but can change what to do with them in the “body” like i do below).

doEveryBuilding(){
  b.doSomething();
}
Advertisement

You can use a lambda or other function object, but not with that syntax without a fairly nasty macro (put the for() loop in the last suggestion in a macro).

// Assuming a member of some class with a "block" member variable, adjust as needed
template<class F> void doEveryBuilding(F func)
{
    for (int i = 0; i < MAX_BLOCK; i++)
        for (int ii = 0; ii < MAX_BLOCK; ii++)
            for (int iii = 0; iii < BUILDING_X; iii++)
                for (int iiii = 0; iiii < BUILDING_Y; iiii++)
                    func(block[i][ii].building[iii][iiii]);
}

doEveryBuilding([](auto &building) { building.doSomething(); });

Or maybe use member function pointers, and with variadic templates, even pass parameters to the member function.

template<class F> void doEveryBuilding(F func)
{
    for (int i = 0; i < MAX_BLOCK; i++)
        for (int ii = 0; ii < MAX_BLOCK; ii++)
            for (int iii = 0; iii < BUILDING_X; iii++)
                for (int iiii = 0; iiii < BUILDING_Y; iiii++)
                    (block[i][ii].building[iii][iiii].*func)();
}
doEveryBuilding(&Building::doSomething);

Alternatively, you could put together an iterator and a container type that provides it from a “begin” and “end”. This is a little more involved, as the 4 loops need to be converted into one, but I believe it should look like this:

// Add a member as needed to point to "blocks" assuming it is not global
class EveryBuildingIterator
{
public:
    typedef Building value_type;
    typedef Building *pointer;
    typedef Building &reference;
    typedef std::forward_iterator_tag iterator_category;
    EveryBuildingIterator(int i, int ii, int iii, int iiii)
        : i(i), ii(ii), iii(iii), iiii(iiii) {}
    EveryBuildingIterator &operator++()
    {
        if (++iiii >= BUILDING_Y)
        {
            iiii = 0;
            if (++iii >= BUILDING_X)
            {
                iii = 0;
                if (++ii >= MAX_BLOCK)
                {
                    ii = 0;
                    ++i;
                }
            }
        }
        return *this;
    }
    EveryBuildingIterator operator++(int)
    {
        auto ret = *this;
        ++(*this);
        return ret;
    }
    reference operator*()
    {
        return block[i][ii].building[iii][iiii];
    }

    friend bool operator == (const EveryBuildingIterator &a, const EveryBuildingIterator &b);
    friend bool operator != (const EveryBuildingIterator &a, const EveryBuildingIterator &b);
private:
    int i, ii, iii, iiii;
};
bool operator == (const EveryBuildingIterator &a, const EveryBuildingIterator &b)
{
    return a.i == b.i && a.ii == b.ii && a.iii == b.iii && a.iiii == b.iiii;
}
bool operator != (const EveryBuildingIterator &a, const EveryBuildingIterator &b)
{
    return !(a == b);
}
// Again add a member as needed to reference "blocks"
class EveryBuilding
{
public:
    typedef EveryBuildingIterator iterator;
    iterator begin() { return iterator(0, 0, 0, 0); }
    iterator end() { return iterator(MAX_BLOCK, MAX_BLOCK, BUILDING_X, BUILDING_Y); }
};

int main() {
    // Probably return EveryBuilding from something, e.g. "world.everyBuilding()"
    for (auto &building : EveryBuilding())
        building.doSomething();
}

Under c++20, where we finally get coroutines, you can have the best of all worlds:

#include <experimental/generator>

std::generator<const Building&> enumBuildings(void)
{
    for (int i = 0; i < MAX_BLOCK; i++)
        for (int ii = 0; ii < MAX_BLOCK; ii++)
            for (int iii = 0; iii < BUILDING_X; iii++)
                for (int iiii = 0; iiii < BUILDING_Y; iiii++)
                    co_yield block[i][ii].building[iii][iiii];
}

for (const auto& building : enumBuildings())
{
}

C++20 can be used by enabling a switch under MSVC, and probably has preview versions in other compilers as well.

And on a side-note, unless the structures used here are pointers/dynamic arrays, you should consider using range-based for-loops instead:

for (const auto& blockRow : block)
	for (const auto& buildings : blockRow)
		for (const auto& buildingRow : buildings)
			for (const auto& building : buildingsRow)
				co_yield building;

Sounds like you want delegates?

In C# one can write

void ForEach<T>(Action<T> action)
{
    for (int i = 0; i < MAX_BLOCK; i++)
        for (int ii = 0; ii < MAX_BLOCK; ii++)
            for (int iii = 0; iii < BUILDING_X; iii++)
                for (int iiii = 0; iiii < BUILDING_Y; iiii++)
                    action(block[i][ii].building[iii][iiii]);
}

In C++ however, this is more complicated because of calling conventions (thiscall vs stdcall vs cdecl) but with some template magic it is absolutely possible.

What I do is using this together with some macro code/ multiple include code to fill the template arguments dynamically. I do this because I don't like to use all the new fancy C++14 and above stuff and rarely use C++11 features. The code here is fully C++99 compilant.

(If you instead want fancy C++ compiler magic, go ahead and skip the rest)

I have declared static inline functions that all share the same function signature. This way I can store any function regardless of it's calling convention and decorators (const) via a proxy function pointer in my delegate struct

template<typename ret do_if(ORDER, _separator) variadic_decl(typename Args, ORDER)> struct InstanceCallContext<ret (variadic_decl(Args, ORDER))>
{
    public:
        /**
         Provides a class member function call context to use in dynamic delegate
        */
        template<class T, ret (T::*type) (variadic_decl(Args, ORDER))> static force_inline ret Functor(void* target do_if(ORDER, _separator) variadic_args(Args, a, ORDER))
        { 
            T* ptr = static_cast<T*>(target);
            return (ptr->*type)(variadic_decl(a, ORDER));
        }
        /**
         Provides an anonymous class member function call context to use for dynamic calling
        */
        template<class T, ret (T::*type) (variadic_decl(Args, ORDER))> static force_inline void AnonymousFunctor(void* target, void** args)
        { 
            T* ptr = static_cast<T*>(target);
            *reinterpret_cast<typename SE::TypeTraits::Const::Remove<typename SE::TypeTraits::Reference::Remove<ret>::Result>::Result*>(args[ORDER]) = (ptr->*type)(variadic_deduce(Args, args, ORDER));

            (void)args;
        }

        /**
         Provides a const class member function call context to use in dynamic delegate
        */
        template<class T, ret (T::*type) (variadic_decl(Args, ORDER)) const> static force_inline ret ConstFunctor(void* target do_if(ORDER, _separator) variadic_args(Args, a, ORDER))
        { 
            T* ptr = static_cast<T*>(target);
            return (ptr->*type)(variadic_decl(a, ORDER));
        }
        /**
         Provides an anonymous const class member function call context to use for dynamic calling
        */
        template<class T, ret (T::*type) (variadic_decl(Args, ORDER)) const> static force_inline void AnonymousConstFunctor(void* target, void** args)
        { 
            T* ptr = static_cast<T*>(target);
            *reinterpret_cast<typename SE::TypeTraits::Const::Remove<typename SE::TypeTraits::Reference::Remove<ret>::Result>::Result*>(args[ORDER]) = (ptr->*type)(variadic_deduce(Args, args, ORDER));

            (void)args;
        }
};

Because of the lack of variadic templates in C++99, I simply declared a macro to solve this for me

#define variadic_args(type_name, args_name, count) JOIN(XP_VARIADIC_PARAMS_INVOKE_ARGSVAL, count)(type_name, args_name, _separator)

#define XP_VARIADIC_PARAMS_INVOKE_ARGSVAL0(type_name, args_name, separator)
#define XP_VARIADIC_PARAMS_INVOKE_ARGSVAL1(type_name, args_name, separator) JOIN(type_name, 0) JOIN(args_name, 0)
#define XP_VARIADIC_PARAMS_INVOKE_ARGSVAL2(type_name, args_name, separator) XP_VARIADIC_PARAMS_INVOKE_ARGSVAL1(type_name, args_name, separator) separator JOIN(type_name, 1) JOIN(args_name, 1)

It expands the provided parameters and repeats them while adding the iteration count.

Finally my delegate struct, also a variadic template

template<typename ret do_if(ORDER, _separator) variadic_decl(typename Args, ORDER)> struct DynamicDelegate<ret(variadic_decl(Args, ORDER))>
{
    public:
        typedef ret ReturnValue;
        typedef ret (*FunctionPointer) (variadic_decl(Args, ORDER));
        typedef ret (*ContextPointer) (void* do_if(ORDER, _separator) variadic_decl(Args, ORDER));

        /**
         A pointer to the internal call context
        */
        force_inline ContextPointer Context() { return context; }
        /**
         An instance pointer when initialized to a member function, null_ptr otherwise
        */
        force_inline void* Target() { return target; }

        /**
         Copy constructor
        */
        force_inline DynamicDelegate(DynamicDelegate const& delegate) : context(delegate.context), target(delegate.target)
        { }
        /**
         Default constructor
        */
        force_inline DynamicDelegate() : context(se_null), target(se_null)
        { }
        /**
         Class constructor initializes this context with given values
        */
        force_inline DynamicDelegate(ContextPointer context, void* target) : context(context), target(target)
        { }
        /**
         Class destructor
        */
        force_inline ~DynamicDelegate()
        { }

        force_inline DynamicDelegate<ret(variadic_decl(Args, ORDER))>& operator=(DynamicDelegate<ret(variadic_decl(Args, ORDER))> const& delegate)
        {
            Bind(delegate.context, delegate.target);
            return *this;
        }

        force_inline operator bool() const { return context != se_null; }
        force_inline bool operator!() const { return !(operator bool()); }

        force_inline ret operator()(variadic_args(Args, a, ORDER)) const 
        { 
            return Invoke(variadic_decl(a, ORDER));
        }

        /**
         Binds the delegate to a new target
        */
        force_inline void Bind(ContextPointer ctx, void* instance)
        {
            context = ctx;
            target = instance;
        }
        /**
         Binds the delegate to a new target
        */
        force_inline void Bind(ContextPointer ctx)
        {
            Bind(ctx, se_null);
        }

        /**
         Calls the function this context is bound to
        */
        force_inline ret Invoke(variadic_args(Args, a, ORDER)) const
        { 
            return context(target do_if(ORDER, _separator) variadic_decl(a, ORDER));
        }

    private:
        ContextPointer context;
        void* target;
};

encapsulates the proxy call pointer together with an optional pointer to an instance of the member it was created for. On invocation, the pointer and arguments are passed to the proxy and indirected to the member function call in the proxy function. I stored the member instance in my delegate class to be able to also forward to static functions and not just member functions but you won't need this I guess.

However, using the InstanceCallContext proxy functions, you could rewrite your code to accept some variadic function pointers

template<complicated macro code here> inline void ForEach(MyPtr action, [optional] variadic args)
{
    for (int i = 0; i < MAX_BLOCK; i++)
        for (int ii = 0; ii < MAX_BLOCK; ii++)
            for (int iii = 0; iii < BUILDING_X; iii++)
                for (int iiii = 0; iiii < BUILDING_Y; iiii++)
                    action(block[i][ii].building[iii][iiii], args);
}
ForEach(InstanceCallContext<void ()>::Functor<MyClass, MyClass::DoStuff>, myArgs);

I know InstanceCallContext is not as cool as C#'s actions but the minimal code requeired to perform this action. I tried a lot of different approaches but this is the best I achieved so far for executing dynamic functions

Shaarigan said:
In C# one can write void ForEach(Action action) { for (int i = 0; i < MAX_BLOCK; i++) for (int ii = 0; ii < MAX_BLOCK; ii++) for (int iii = 0; iii < BUILDING_X; iii++) for (int iiii = 0; iiii < BUILDING_Y; iiii++) action(block[i][ii].building[iii][iiii]); }

Well, seeing as C# has coroutines as well..

IEnumerator<Building> enumBuildings()
{
    for (int i = 0; i < MAX_BLOCK; i++)
    {
        // and so on…
        
        yield return block[i][ii].building[iii][iiii];
    }
}

foreach (var building in enumBuildings())
{
}

Sounds like what you are doing is a lot of overhead for poorly (no offense, not directed at your code) simulating a basic language feature that has been there since for eternety (as opposed to c++ where the support simply isn't fully there yet even know)

This topic is closed to new replies.

Advertisement