dimanche 27 janvier 2019

Template function that supports references, values, and pointers?

I'm wrapping a C function in a C++ callable. The C function accepts a function pointer (with state). A code sample says a thousand words so...

    template<typename IteratorFunctor>
    MatchCollection match(IteratorFunctor& iterator) const
    {
        // type that the functor returns, and related types
        using TIn = decltype(iterator());
        using TNoRef = typename std::remove_reference<TIn>::type;
        using TYesRef = typename std::add_lvalue_reference<TNoRef>::type;
        using TStored = typename std::conditional<std::is_reference<TIn>::value, std::reference_wrapper<TNoRef>, TIn>::type;

        // store this on the stack in this function, and pass a pointer to it into the C library
        // the C callback will pass back the pointer, and we can get at all this stuff from within the lambda
        struct CallbackContext
        {
            bool isFirst;               // is this the first iteration?
            IteratorFunctor& iterator;  // reference to the iterator in a place we can get to from inside the C function pointer callback
            TStored current;            // current value (either an actual value stored on the stack, or a reference wrapper)
        };

        detail::setmatch_TokenIterator cFunctionPtr = [](void* pContext) -> detail::setmatch_String
        {
            CallbackContext& context = *((CallbackContext*) pContext);

            // on the first iteration, we return the value already fetched (need to do this to support things that
            // aren't DefaultConstructable). On subsequent iterations, call the functor again.
            if(context.isFirst)
                context.isFirst = false;
            else
                context.current = context.iterator();

            // this is needed for supporting both references as reference_wrappers and value types. we take a reference
            // which forces reference_wrapper to call its conversion operator and is basically a no-op for value types
            // (something like context.current.length would fail for reference_wrapper)
            TYesRef current = context.current;

            // stop iteration if functor returns anything with length 0
            if(current.length() == 0)
                return detail::setmatch_String{nullptr, 0};
            else
                return detail::setmatch_String{current.data(), current.length()};
        };

        // create the context and make the first call to the iterator
        CallbackContext context{true, iterator, iterator()};

        // and then call the C function
        detail::setmatch_MatchCollection temp;
        const char* err = detail::setmatch_match(db, cFunctionPtr, &context, &temp);
        if(err) throw std::runtime_error(err);
        return MatchCollection(temp.matchedIds, temp.count);
    }

So this supports functors that return either references or value types, however it does not support functors that return pointers. I'd love to also allow the functor to return a pointer to something.

Is there a good way to do this using C++11 features (and no dependencies or weird compiler hacks, since this is part of the public API)?

Aucun commentaire:

Enregistrer un commentaire