c++castinglegacy-code

Can I check the actual type of void*?


I have to use a legacy C library in my C++ code. One of the functions of that library looks like this:

int legacyFunction(int (*userDefinedPredicateFunction)(void*), void* structure, otherArgs...);

This legacyFunction() calls userDefinedPredicateFunction() inside itself passing structure as argument to it. I have multiple custom predicate functions to be used with the above function. Each of these functions works different from the others and expects an argument to be of a strictly defined type to work properly:

int userDefinedPredicateFunction1(void* structure)
{
    const expectedType1* const s = reinterpret_cast<expectedType1*>(structure);
    // Check s conditions...
}

int userDefinedPredicateFunction2(void* structure)
{
    const expectedType2* const s = reinterpret_cast<expectedType2*>(structure);
    // Check s conditions...
}

int userDefinedPredicateFunction3(void* structure)
{
    const expectedType3* const s = reinterpret_cast<expectedType3*>(structure);
    // Check s conditions...
}

The problem is that these predicate functions are not safe to use - passing an argument of not the expected type to them will lead to an undefined mess. I need to somehow check the type of the argument and throw if it is not the expected type. structure is not polymorphic, so I cannot use dynamic_cast here.

The first thing comes to mind is to use a wrapper function like this one:

int legacyFunctionWrapper1(const Type1& structure, otherArgs...)
{
    return legacyFunction(
        userDefinedPredicateFunction1, &structure, otherArgs...);
}

This will let structure only to be of a one certain type expected by the predicate function, but a dedicated wrapper function is needed to be written to be used with each predicate function, which is undesirable.

Is there more elegant way to check the actual type of void* pointer?


Solution

  • I think you are on the right path with that wrapper function. You can save yourself some work by making it a template. This should work:

    using legacy_predicate = int (*)(void*);
    
    template<class T>
    void call_legacy(int (*predicate)(T*), T* obj, int otherargs)
    {
        using pair_type = std::pair<int(*)(T*), T*>;
        pair_type realargs = std::make_pair(predicate, obj);
        legacy_predicate wrapper = +[](void* unsafe) -> int {
            pair_type& realargs = *static_cast<pair_type*>(unsafe);
            return realargs.first(realargs.second);
        };
        legacyFunction(wrapper, &realargs, otherargs);
    }
    

    Two things of note:

    1. I avoided reinterpret-casting the function pointer since that is technically undefined behavior (though it should work in practice). This introduces some indirection and may be a bit slower

    2. The trick is to turn a stateless lambda into a function pointer with the unary + operator

    To make up for the indirection and maybe modernize the whole thing a bit, consider this:

    template<class Functor>
    void call_legacy(Functor predicate, int otherargs)
    {
        legacy_predicate wrapper = +[](void* unsafe) -> int {
            Functor* predicate = static_cast<Functor*>(unsafe);
            return (*predicate)();
        };
        legacyFunction(wrapper, &predicate, otherargs);
    }
    

    This allows you to pass arbitrary functors, not just function pointers. So you can use it with lambdas or whatever you want. To get the old pattern, you wrap the object at the call site, maybe make it into a overload. Like this:

    template<class T>
    void call_legacy(int (*predicate)(T*), T* obj, int otherargs)
    {
        // dispatch to overloaded interface described above
        return call_legacy([predicate, obj]() -> int {
              return predicate(obj);
        }, otherargs);
    }
    

    Warning

    This only works if legacyFunction does not store the passed void* beyond the runtime of the function. Beware of dangling pointers.

    Alternative

    As I've mentioned, casting function pointers is undefined behavior, but it tends to work in simple cases. Rule of thumb: If your platform supports GTK+, it supports simple function pointer casts (because Gtk even does horrible things like changing the number of function arguments).

    So this is the zero-overhead version:

    using legacy_predicate = int (*)(void*);
    template<class T>
    void call_legacy(int (*predicate)(T*), T* obj, int otherargs)
    {
        legacy_predicate unsafe = reinterpret_cast<legacy_predicate>(predicate);
        legacyFunction(unsafe, obj, otherargs);
    }