在没有 lambda 的情况下将模板化函数作为方法参数传递?

问题描述

我确实希望能够在不声明 lambda 的情况下使用 extFunction 或 std::maxstd::min 作为 square 方法的参数:

template<typename T>
T extFunction(T a,T b)
{
    return a;
}

class Stuff
{
public:
    template <typename F>
    int square(int num,int num2,F&& func) 
    {
        return func(num,num2);
    }
};

int main()
{
    Stuff s;
    std::cout << s.square(1,2,std::max<int>) << std::endl;
    return 0;
}

但是编译器 (gcc 11.1) 告诉我:

函数不明确:“无法推导出模板参数'F'”

有没有一种简单的方法可以在没有 lambda 的情况下做到这一点?

编辑:

也许展示如何使用 lambdas 做到这一点会很有趣:

std::cout << s.square(1,[](auto&& a,auto&& b){return std::max(a,b);}) << std::endl;
    
std::cout << s.square(1,auto&& b){return std::min(a,auto&& b){return extFunction(a,b);}) << std::endl;

输出

Program returned: 0
Program stdout

2
1
1

解决方法

您不应该获取标准库函数的地址。详情请看这里:

Can I take the address of a function defined in standard library?

因此,除了将 std::max 打包到函数对象(即 lambda、函子)或函数中之外,没有其他简单的方法。

,

std::maxstd::min 的问题在于它们模棱两可,正如您所看到的 here。有几个重载,您必须告诉编译器您想使用哪一个。


  • 在现代 C++ 中,您实际上会将所需的重载包装到 lambda函子 中。

  • 如果由于某种原因您无法使用 lambda 的旧代码(例如 C++03),您可以执行 static_cast 来告诉使用如下重载的编译器

    template <typename F,typename... Args>
    auto func(F f,Args... args) {
      return f(args...);
    }
    
    int main() {
      std::cout << func(static_cast<double const&(*)(double const&,double const&)>(std::max),3.0,2.0) << std::endl;
      return EXIT_SUCCESS;
    }
    

    Try it here!

    正如用户 JeJo 已经提到的,这被认为是不良做法(自 C++20 起),可能导致未指定的 (not undefined!) 行为,如 std::minstd::max 不可寻址。 他所指的标准部分最近才在基于 this draft 的 C++20 中引入。可以在 here 上找到用户 Barry 发布的另一篇关于此的帖子。

,

在 C++ 中,当你想传递一个 invocable 时,它​​是多态的,因为它有多个潜在的签名,最好的方法是将它包装在一个对象中。重载集不是一等公民。不过有proposals

当可调用对象是您提供给用户的东西时,将其定义为对象很简单:

inline constexpr auto extFunction = []<typename T>(T a,T b) -> T {
     return a;
};

当你有一个重载集时,你必须把它包装起来。这很烦人,但您可以使用宏减少样板:

#define FWD(x) static_cast<decltype(x)&&>(x)
#define RETURNS(expr) noexcept(noexcept(expr)) -> decltype(expr) { return expr; }
#define OVERLOADS_OF(name) [&](auto&& ...args) RETURNS(name(FWD(args)...))

您的示例如下所示:

std::cout << s.square(1,2,OVERLOADS_OF(std::max)) << std::endl;        
std::cout << s.square(1,OVERLOADS_OF(std::min)) << std::endl;
std::cout << s.square(1,extFunction) << std::endl;

为了完整起见,在相当狭窄的情况下,接收者还可以定义函数指针重载:

int square(int num,int num2,int (*func)(int,int));
s.square(1,extFunction); // now works

模板推导是从一个函数指针开始的,所以extFunction的模板参数是推导的。

正如其他答案中提到的,对于标准库,您要避免依赖大多数标准库函数的特殊性,因为标准只承诺您可以调用它们,并不保证它们的形式。