C++17:使用通用可变参数 lambda 包装可调用对象
C++17: Wrapping callable using generic variadic lambda
我想将任何类型的可调用对象(例如 lambda)透明地包装在另一个可调用对象中以注入额外的功能。包装器的类型应具有与原始可调用对象相同的特征:
- 相同的参数类型
- 相同return类型
- 完美转发传递的参数
- 在 SFINAE 结构中使用相同的行为
我尝试使用通用可变参数 lambda 作为包装器:
#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
}
正如 static_assert
中的评论所指出的,包装器可调用文件的调用方式与原始可调用文件不同。需要更改什么才能实现所需的功能?
给出的示例是使用 Visual Studio 2017 (msvc 15.9.0) 编译的。
这可能是 MSVC 的 std::invoke_result
或 std::is_invocable
实现中的错误(即使使用 Visual Studio 15.9.2,我也可以在此处重现该问题)。您的代码 works fine with clang (libc++) and gcc 我看不出有任何理由不应该这样做。然而,无论如何,你并不真的需要 std::invoke_result
,你可以让你的 lambda 推断出 return 类型:
template<class TCallable>
auto wrap(TCallable&& callable) {
return [callable = std::forward<TCallable>(callable)](auto&&... args) -> decltype(auto) {
std::cout << "This is some additional functionality" << std::endl;
return callable(std::forward<decltype(args)>(args)...);
};
}
然后 also seems to work fine with MSVC…
编辑:正如 Piotr Skotnicki 在下面的评论中所指出的,decltype(auto)
will prohibit SFINAE。要解决此问题,您可以改用尾随 return 类型:
template<class TCallable>
auto wrap(TCallable&& callable) {
return [callable = std::forward<TCallable>(callable)](auto&&... args) -> decltype(callable(std::forward<decltype(args)>(args)...)) {
std::cout << "This is some additional functionality" << std::endl;
return callable(std::forward<decltype(args)>(args)...);
};
}
打字会多一些,但应该可以很好地与 SFINAE 和 also seems to work fine with MSVC…
我想将任何类型的可调用对象(例如 lambda)透明地包装在另一个可调用对象中以注入额外的功能。包装器的类型应具有与原始可调用对象相同的特征:
- 相同的参数类型
- 相同return类型
- 完美转发传递的参数
- 在 SFINAE 结构中使用相同的行为
我尝试使用通用可变参数 lambda 作为包装器:
#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
}
正如 static_assert
中的评论所指出的,包装器可调用文件的调用方式与原始可调用文件不同。需要更改什么才能实现所需的功能?
给出的示例是使用 Visual Studio 2017 (msvc 15.9.0) 编译的。
这可能是 MSVC 的 std::invoke_result
或 std::is_invocable
实现中的错误(即使使用 Visual Studio 15.9.2,我也可以在此处重现该问题)。您的代码 works fine with clang (libc++) and gcc 我看不出有任何理由不应该这样做。然而,无论如何,你并不真的需要 std::invoke_result
,你可以让你的 lambda 推断出 return 类型:
template<class TCallable>
auto wrap(TCallable&& callable) {
return [callable = std::forward<TCallable>(callable)](auto&&... args) -> decltype(auto) {
std::cout << "This is some additional functionality" << std::endl;
return callable(std::forward<decltype(args)>(args)...);
};
}
然后 also seems to work fine with MSVC…
编辑:正如 Piotr Skotnicki 在下面的评论中所指出的,decltype(auto)
will prohibit SFINAE。要解决此问题,您可以改用尾随 return 类型:
template<class TCallable>
auto wrap(TCallable&& callable) {
return [callable = std::forward<TCallable>(callable)](auto&&... args) -> decltype(callable(std::forward<decltype(args)>(args)...)) {
std::cout << "This is some additional functionality" << std::endl;
return callable(std::forward<decltype(args)>(args)...);
};
}
打字会多一些,但应该可以很好地与 SFINAE 和 also seems to work fine with MSVC…