Ad

Defer/cancel Execution Of Functions And Analyze Their Arguments

- 1 answer

I'm trying to write external draw call optimization, and for that I need to collect hooked calls, store their arguments to analyze them later on. I've been able to make deferred calls, and somewhat readable arguments, stored in tuple, but I need to read arguments from base class and after thorough googling I can't find anything applicable. I'll work with array of IDelegateBase mostly, and it would be very inconvenient to convert them to Delegate<...> with full signature, when I mostly would read just one argument. Therefore, I need virtual templated method in IDelegateBase, which would return n-th argument. But virtual templated methods are impossible, so probably I'd have to have templated method in base class, which would call non-template (boost::any?) virtual method and cast it's result, I suppose. But, anyway, I can't get n-th element from tuple via runtime variable.

#include <functional>
#include <iostream>

class IDelegateBase
{
public:
    virtual void invoke() { }
};
template <typename T, typename... Args>
class Delegate : public IDelegateBase
{
private:
    void (*_f)(Args...);
    std::tuple<Args...> _args;
public:
    Delegate(T& f, Args &...args)
        : _f(f), _args(args...) { }
    void invoke() final
    {
        std::apply(_f, _args);
    }
};
void a() { std::cout << "a called;\n"; }
void b(int x) { std::cout << "b called with " << x << "\n"; }
void c(int x, float y) { std::cout << "c called with " << x << ", " << y << "\n"; }
int main()
{
    
    IDelegateBase* d = new Delegate(a);
    d->invoke();
    int i = 42;
    d = new Delegate(b, i);
    d->invoke();
    i = 21;
    float f = 0.999;
    d = new Delegate(c, i, f);
    d->invoke();
    // I need something like this:
    auto test = d->getArgument<float>(1);
};
Ad

Answer

You could provide a virtual function returning void* and use it in a template, but type safety goes down the drain: Should you ever get the type wrong, you'll end up with undefined behaviour.

For getting element using an index you can use a recursive helper template that compares with one index per recursive call.

class IDelegateBase
{
public:
    virtual void invoke() { }

    template<class T>
    T const& getArgument(size_t index) const
    {
        return *static_cast<T const*>(getArgumentHelper(index));
    }
protected:
    virtual void const* getArgumentHelper(size_t index) const = 0;
};

template <typename T, typename... Args>
class Delegate : public IDelegateBase
{
private:
    void (*_f)(Args...);
    std::tuple<Args...> _args;
public:
    Delegate(T& f, Args &...args)
        : _f(f), _args(args...) { }
    void invoke() final
    {
        std::apply(_f, _args);
    }
protected:
    void const* getArgumentHelper(size_t index) const override
    {
        return GetHelper<0>(index, _args);
    }
private:
    template<size_t index>
    static void const* GetHelper(size_t i, std::tuple<Args...> const& args)
    {
        if constexpr (sizeof...(Args) > index)
        {
            if (index == i)
            {
                return &std::get<index>(args);
            }
            else
            {
                return GetHelper<index + 1>(i, args);
            }
        }
        else
        {
            throw std::runtime_error("index out of bounds");
        }
    }
};

The use of if constexpr is needed here, since std::get does not compile if a of tuple index out of bounds is used.

Ad
source: stackoverflow.com
Ad