Home > OS >  std::function - could not deduce template argument for ... (variadic template functions)
std::function - could not deduce template argument for ... (variadic template functions)

Time:08-25

I'm writing a simple "wrapper library" around another library and I'm having issues with std::function not being able to deduce template arguments for variadic template functions. To make things more concrete:

  • There's a library (it's a HTTP server) that I'm planning to "expand" with more functionality and to make it more convenient to use by "other code users" (automatic serialization of data, writing controller functions based on needs etc.). Basically cutting down on a lot of boilerplate code. From now on I'm calling this one simply "library". It cannot be touched or changed.
  • There's the code that "expands" the functionality above that I write myself and it's where I have the problem. From now on I call this a "wrapper library".
  • There's the "user code", that is - any code that uses the "wrapper library". I've included examples what I'd like it to look like and I'd rather keep it as simple as possible, especially when it comes to unnecessary template parameters bloat (not having to explicitly pass template arguments when it can be deduced by the compiler etc.). See main in the code below for examples.

Here's the complete example (that doesn't compile):

#include <type_traits>
#include <variant>
#include <vector>

/*** Library code starts here ***/
// Library handler function definition to be registered. This cannot be changed.
// For simplicity I made the function arguments (int, int) to denote that this
// handler function does take some arguments passed later by the library.
// In reality these are simple, non-templated structs.
using LibraryHandler = std::function<void(int, int)>;
std::vector<LibraryHandler> g_libraryHandlers;
void RegisterLibraryHandler(LibraryHandler handler)
{
    // Library code registering passed handler. This cannot be changed.
    g_libraryHandlers.push_back(handler);
}
/*** End of library code ***/


/*** My "wrapper library" starts here ***/
// ReturnType is the type returned by the functions written by "wrapper library users".
// Types... is the list of all possible types held by 'ReturnType' that a given function is allowed to return.
template<typename T, typename... Types>
static inline constexpr bool isConstructible = (std::is_constructible_v<T, Types> || ...);
template<typename T, typename... Types>
static inline constexpr bool isAssignable = (std::is_assignable_v<T&, Types> || ...);

template<typename... Types>
class ReturnType
{
public:
    template<typename T, std::enable_if_t<
            isConstructible<T, Types...> &&
            isAssignable<T, Types...>,
        int> = 0>
    static ReturnType Build(const T& data)
    {
        return ReturnType(data);
    }
private:
    template<typename T>
    ReturnType(const T& data) : m_data(data) { }
    std::variant<Types...> m_data;
};

template<typename T>
class ParamType
{
public:
    ParamType() { }
    const T& GetData() const { return m_data; }
private:
    T m_data;
};

/*** This is where my problem is - how to write these functions to allow for
     different logic (based on passed function signature) inside nested lambda? ***/
// Version registering functions taking no arguments
template<typename... Types>
void RegisterFunction(std::function<ReturnType<Types...>()> func)
{
    auto wrapperLambda = [func](int a, int b) { // Construct lambda to conform with library interface ('LibraryHandler')
        // Logic specific for 'func' with signature: ReturnType<Types...>() - step before call
        func();
        // Logic specific for 'func' with signature: ReturnType<Types...>() - step after call
    };
    RegisterLibraryHandler(wrapperLambda); // Register lambda inside library
}

// Version registering functions taking arguments through 'ParamType'
template<typename... Types, typename TParam>
void RegisterFunction(std::function<ReturnType<Types...>(const ParamType<TParam>)> func)
{
    auto wrapperLambda = [func](int a, int b) { // Construct lambda to conform with library interface ('LibraryHandler')
        // Logic specific for 'func' with signature: ReturnType<Types...>(const ParamType<TParam>) - step before call
        func();
        // Logic specific for 'func' with signature: ReturnType<Types...>(const ParamType<TParam>) - step after call
    };
    RegisterLibraryHandler(wrapperLambda); // Register lambda inside library
}

// Version registering functions taking library arguments directly
template<typename... Types, typename TParam>
void RegisterFunction(std::function<ReturnType<Types...>(int, int)> func)
{
    auto wrapperLambda = [func](int a, int b) { // Construct lambda to conform with library interface ('LibraryHandler')
        // Logic specific for 'func' with signature: ReturnType<Types...>(int, int) - step before call
        func(a, b);
        // Logic specific for 'func' with signature: ReturnType<Types...>(int, int) - step after call
    };
    RegisterLibraryHandler(wrapperLambda); // Register lambda inside library
}
/*** End of my "wrapper library" ***/


/*** This is how I'd like the "users of the wrapper library" to write the code ***/
struct SomeReturnData { char a; char b; };
struct OtherReturnData { char c; char d; };
struct SomeParameterType { int p; };

// Example 1 - function only ever returning a single type, taking no arguments
ReturnType<SomeReturnData> UserFunctionNoArgs()
{
    SomeReturnData data{ 'x', 'y' };
    return ReturnType<SomeReturnData>::Build(data);
}

// Example 2 - function taking parameter 'SomeParameterType' and returning 'SomeReturnData' or 'OtherReturnData'
// based on contents of passed argument.
ReturnType<SomeReturnData, OtherReturnData> UserFunctionArgs(const ParamType<SomeParameterType> param)
{
    if (param.GetData().p == 1000)
        return ReturnType<SomeReturnData, OtherReturnData>::Build(SomeReturnData());
    return ReturnType<SomeReturnData, OtherReturnData>::Build(OtherReturnData());
}

// Example 3 - function requesting library arguments to be passed directly
ReturnType<SomeReturnData, OtherReturnData> UserHandlingLibraryDirectly(int libraryArg1, int libraryArg2)
{
    if (libraryArg1 == libraryArg2)
        return ReturnType<SomeReturnData, OtherReturnData>::Build(SomeReturnData());
    return ReturnType<SomeReturnData, OtherReturnData>::Build(OtherReturnData());
}

int main()
{
    // User registers written functions in the "wrapper library".
    // This includes any 'regluar functions'...
    RegisterFunction(UserFunctionNoArgs);
    RegisterFunction(UserFunctionArgs);
    RegisterFunction(UserHandlingLibraryDirectly);

    // ... or lambdas with captures (all possible parameter combinations from examples above apply).
    int someInt = 123;
    auto userLambda = [someInt]() -> ReturnType<SomeReturnData, OtherReturnData> {
        if(someInt < 100)
            return ReturnType<SomeReturnData, OtherReturnData>::Build(SomeReturnData());
        return ReturnType<SomeReturnData, OtherReturnData>::Build(OtherReturnData());
    };

    return 0;
}

My issue is with the RegisterFunction above - I'd basically want to write it the way it is above, which is obviously not possible (the compiler complains about not being able to deduce template parameters). My goal is to not change / cut down on possibilities / make any more complicated anything that's inside main.

CodePudding user response:

Lambdas are not std::function, so cannot be deduced. Fortunately, std::function has CTAD (c 17) allowing to solve your issue with an extra overload:

template<typename Func>
void RegisterFunction(Func func)
{
    RegisterFunction(std::function{func}); // forward to overload taking std::function
}

Demo

  • Related