【问题标题】:Passing a templated function as method argument without lambdas?在没有 lambda 的情况下将模板化函数作为方法参数传递?
【发布时间】:2021-10-03 22:01:28
【问题描述】:

我确实希望能够在不声明 lambda 的情况下使用 extFunction 或 std::max 或 std::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,2,[](auto&& a, auto&& b){return std::max(a,b);}) << std::endl;
    
std::cout << s.square(1,2,[](auto&& a, auto&& b){return std::min(a,b);}) << std::endl;
    
std::cout << s.square(1,2,[](auto&& a, auto&& b){return extFunction(a,b);}) << std::endl;

输出:

Program returned: 0
Program stdout

2
1
1

【问题讨论】:

  • std::max&lt;int&gt; 有几个重载,const int&amp;(*)(const int&amp;, const int&amp;) 和 int (*) (std::initializer_list&lt;int&gt;)。
  • 为什么要这样做?是好奇还是潜意识的要求?
  • @JeffGarrett 我有两种算法要执行,它们是相同的,除了一个需要一组值的最大值,另一个需要最小值。显然我不想把算法写两次,因为区别只是调用一个函数或另一个。

标签: c++ templates lambda function-pointers function-templates


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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


    • 在现代 C++ 中,您实际上会将希望的重载包装到 lambda 或 functor 中。

    • 如果由于某种原因您遇到无法使用 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::min 和 std::max 是不可寻址。 他所指的标准部分是最近才在基于this draft 的C++20 中引入的。可以在here 找到用户 Barry 的另一篇帖子。

    【讨论】:

    • 您可以从其他答案的链接中阅读,您的解决方案将是 UB 用于(不允许)std 函数。
    • @Jarod42 这部分标准仅在 this draft 之后的 C++20 中引入,并且行为只是未指定而不是未定义。我添加了相应的备注。
    【解决方案3】:

    在 C++ 中,当您想要传递一个可调用对象时,它是多态的,因为它具有多个潜在的签名,最好的方法是将它包装在一个对象中。重载集不是一等公民。不过有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, 2, OVERLOADS_OF(std::min)) << std::endl;
    std::cout << s.square(1, 2, extFunction) << std::endl;
    

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

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

    模板推导是从函数指针开始的,所以extFunction的模板参数被推导出来。

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

    【讨论】:

      猜你喜欢
      • 2017-06-14
      • 2015-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-04
      • 2018-11-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多