C++ 17:使用泛型variadic lambda包装可调用

Cyb*_*ran 5 c++ lambda wrapper perfect-forwarding c++17

我想在另一个callable中透明地包装任何类型的可调用(例如lambda)以注入其他功能.包装器的类型应该与原始可调用的类型相同:

  • 相同的参数类型
  • 相同的退货类型
  • 完美转发传递的参数
  • 在SFINAE构造中使用时的行为相同

我试图使用泛型variadic lambdas作为包装:

#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
}
Run Code Online (Sandbox Code Playgroud)

正如对static_asserts 的注释所指出的那样,包装器callables的调用方式与原始callable的调用方式不同.需要改变什么才能实现所需的功能?

给出的示例是使用Visual Studio 2017(msvc 15.9.0)编译的.

Mic*_*zel 7

这可能是MSVC实现中的错误std::invoke_resultstd::is_invocable(我甚至可以使用Visual Studio 15.9.2重现此问题).你的代码使用clang(libc ++)和gcc工作正常,我没有看到任何理由不应该这样做.但是,你真的不需要std::invoke_result这里,你可以让你的lambda推断出返回类型:

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)...);
    };
}
Run Code Online (Sandbox Code Playgroud)

那么MSVC似乎也能正常工作 ......

编辑:正如Piotr Skotnicki在下面的评论中指出的那样,decltype(auto)将禁止SFINAE.要解决此问题,您可以使用尾随返回类型:

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)...);
    };
}
Run Code Online (Sandbox Code Playgroud)

这将是一个更多的打字,但应该与SFINAE一起使用,并且似乎也可以与MSVC一起使用 ...

  • ` - > decltype(auto)`不是SFINAE友好的iirc,你应该使用` - > decltype(callable(decltype(args)(args)...))` (2认同)