I want to wrap a callable of any type (e.g. a lambda) transparently inside another callable to inject additional functionality. The wrapper's type should have the same characteristics as the original callable:
- Identical parameter types
- Identical return type
- Perfect forwarding of passed arguments
- Same behaviour when used in SFINAE constructs
I attempted to use generic variadic lambdas as wrappers:
#include <iostream>
#include <type_traits>
template<class TCallable>
auto wrap(TCallable&& callable) {
return [callable = std::forward<TCallable>(callable)](auto&&... args) -> std::invoke_result_t<TCallable,decltype(args)...> {
std::cout << "This is some additional functionality" << std::endl;
return callable(std::forward<decltype(args)>(args)...);
};
}
int main(int argc, char *argv[])
{
auto callable1 = []() {
std::cout << "test1" << std::endl;
};
auto callable2 = [](int arg) {
std::cout << "test2: " << arg << std::endl;
};
auto wrapped1 = wrap(callable1);
auto wrapped2 = wrap(callable2);
static_assert(std::is_invocable_v<decltype(callable1)>); // OK
static_assert(std::is_invocable_v<decltype(wrapped1)>); // fails
static_assert(std::is_invocable_v<decltype(callable2), int>); // OK
static_assert(std::is_invocable_v<decltype(wrapped2), int>); // fails
}
As the comments on the static_assert
s indicate, the wrapper callables are not invocable in the same way as the original callables. What needs to be changed in order to achieve the desired functionality?
The given example was compiled using Visual Studio 2017 (msvc 15.9.0).
-> decltype(auto)
is not SFINAE-friendly iirc, you should probably use-> decltype(callable(decltype(args)(args)...))
– Sawtelle