C ++ 17:使用通用可变参数lambda包装可调用

时间:2018-11-24 18:08:40

标签: c++ lambda wrapper c++17 perfect-forwarding

我想将任何类型的可调用对象(例如lambda)透明地包装在另一个可调用对象中,以注入其他功能。包装器的类型应具有与原始可调用者相同的特征:

  • 相同的参数类型
  • 相同的返回类型
  • 完美转发传递的参数
  • 在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)进行编译。

1 个答案:

答案 0 :(得分:7)

这可能是MSVC的std::invoke_resultstd::is_invocable实现中的错误(即使在Visual Studio 15.9.2中,我也可以在此处重现该问题)。您的代码works fine with clang (libc++) and 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)...);
    };
}

然后是also seems to work fine with MSVC

编辑:如Piotr Skotnicki在下面的评论decltype(auto) will prohibit 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)...);
    };
}

wich将有更多输入,但是应该可以与SFINAE和also seems to work fine with MSVC一起使用...