如何在函数定义中定义和使用 std::less 作为模板参数?

How to define and use std::less as a template argument in a function definition?

提问人:wcochran 提问时间:7/14/2023 最后编辑:JeJowcochran 更新时间:7/14/2023 访问量:99

问:

提供并用作模板参数的正确 C++ 语法是什么?std::less

#include <iostream>
#include <functional>

template<typename CMP>
float order(float a, float b)
{
    return CMP(a, b) ? a : b;               // COMPILE ERROR                                                               
    // return CMP::operator()(a,b) ? a : b; //  another try
    // return (a CMP b) ? a : b;            // ¯\_(ツ)_/¯
}

int main() 
{
    const float A = order<std::less<float>>(3.1, 7.2);  // COMPILE ERROR
    const float B = order<std::greater<float>>(3.1, 7.2);
    std::cout << "A=" << A << "\n";
    std::cout << "B=" << B << "\n";
    return 0;
}

编译错误:

$ g++ -std=c++17 foo.cpp -o foo

foo.cpp:6:12: error: no matching constructor for initialization of 'std::__1::less<float>'
foo.cpp:12:21: note: in instantiation of function template specialization 'order<std::__1::less<float> >' requested here
const float A = order<std::less<float>>(3.1, 7.2);
                ^
C++ 模板 C++17 函子 函数模板

评论

1赞 UnholySheep 7/14/2023
由于是类型名称,因此您必须先创建一个对象,例如:CMPreturn CMP{}(a,b) ? a : b;
0赞 user4581301 7/14/2023
读取错误消息 no matching constructor 表示编译器正在尝试用作构造函数,而不是调用调用运算符。答案和上面的评论解释了原因。CMP(a,b)
0赞 alfC 7/14/2023
设计良好的接口不会阻止使用有状态比较谓词。,并且它还给出了更优雅的实现。template<typename CMP> float order(float a, float b, CMP cmp = {}) { return cmp(a,b) ? a : b; }
0赞 Amolgorithm 7/14/2023
@alfC 如果只有显式构造函数,这仍然有效吗?CMP
0赞 alfC 7/14/2023
@Amolgorithm,是的。但是,您将需要通过一种方法来构建它。这是正确的。无论如何,我认为,您的疑问是,为什么大多数人不愿将其用作成语。godbolt.org/z/41GrcjnTG

答:

4赞 Amolgorithm 7/14/2023 #1

你可以像这样返回它:

return CMP{}(a,b) ? a : b;

这是因为 的构造函数不采用任何参数。std::less

CMP是一个类型,因此您需要先创建此类型的对象 ,然后才能调用临时运算符进行比较。CMP{}()CMP

上述解决方案的作用是:它实例化一个对象,然后使用参数和 调用。然后,此运算符函数采用两个参数,如果是较小的参数,则返回 true(布尔表达式)。然后,通过三元检查,返回 和 之间的较小值。std::lessoperator()abaab

评论

0赞 wcochran 7/14/2023
你认为大多数 C++ 编译器会优化对象构造并只生成一个简单的比较指令吗?
0赞 Amolgorithm 7/14/2023
@wcochran 不确定。如果他们是现代 C++ 编译器,他们应该能够。我只知道 MS Visual Studio 会这样做。我不确定 GCC 和 Clang。
3赞 JeJo 7/14/2023 #2

提供并用作模板参数的正确 C++ 语法是什么?std::less

or 是函子(即函数对象)。这意味着,在调用之前,您需要默认构造它们。因此,return 语句必须是std::lessstd::greateroperator()

return CMP{}(a,b) ? a : b;
//     ^^^^^^ ----> default construct the function object

但是,如果要保存一些键入,则可以对 .或者,您可以提供一个模板参数(即),以使其更通用(如果有意):ordertypename T

template<template<typename> class CMP, typename T>
//       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ --> template-template argument
constexpr auto order(T a, T b) 
{
    return CMP{}(a, b) ? a : b;
}

现在你把 asorder

const auto A = order<std::less>(3.1, 7.2); 
//                  ^^^^^^^^^^^ -----> only specify the funnctor
const auto B = order<std::greater>(3.1, 7.2);
//                  ^^^^^^^^^^^^^^ --> only specify the funnctor

评论

0赞 wcochran 7/14/2023
很好,但嵌套模板有点吓到我了。
1赞 HolyBlackCat 7/14/2023
我不会这样做。并非所有比较器都是模板,如果我使用的库只接受模板化,我会非常恼火。
2赞 alfC 7/14/2023 #3

设计良好的接口不会阻止使用有状态比较谓词。 而且,您可以免费获得其他替代方案的更好实现。

template<typename CMP> float order(float a, float b, CMP cmp = {}) {
   return cmp(a, b) ? a : b;
}

注1:

尽管人们普遍认为,如果不能默认构造,语法仍然有效;但是,当然,您需要通过一种方法来构建它。 这很好。CMP

测试:

#include <iostream>
#include <functional>

template<typename CMP>
float order(float a, float b, CMP cmp = {}) {
    return cmp(a,b) ? a : b;                                            
}

template<class T>
struct LESS : std::less<T> {
    LESS() = delete;
    LESS(const char*) {}
};

int main() {
    const float A = order<std::less   <float>>(3.1, 7.2);
    const float B = order<std::greater<float>>(3.1, 7.2);
    const float C = order<     LESS   <float>>(3.1, 7.2, "less");
    // const float D = order<     LESS   <float>>(3.1, 7.2);  // compile error, good!

    std::cout << "A=" << A << "\n";
    std::cout << "B=" << B << "\n";
    return 0;
}

注2:

设计得更好的界面将具有默认顺序:

template<typename CMP = std::less<float> >
float order(float a, float b, CMP cmp = {}) {
    return cmp(a,b) ? a : b;                                            
}

注3:

从技术上讲,如果类具有显式默认构造函数,则需要更详细的东西才能工作,但我不会以这种方式实现,因为如果默认构造函数是显式的,那么它必须有充分的理由,并且我们基本上是在覆盖类设计器的预期行为。

template<typename CMP = std::less<float> >
float order(float a, float b, CMP cmp = CMP{}) {  // MMM, good idea?
    return cmp(a,b) ? a : b;                                            
}

奖金:

有些人(包括 STL、https://en.cppreference.com/w/cpp/algorithm/sort)不能容忍默认参数:

template<typename CMP>
float order(float a, float b, CMP cmp) {
    return cmp(a,b) ? a : b;                                            
}

template<typename CMP>
float order(float a, float b) {
    return order<CMP>(a, b, {}); // or CMP{});
}

float order(float a, float b) {
    return order<std::less<float>>(a, b);
}

评论

0赞 wcochran 7/14/2023
我的动机是在这里使用C宏的C++模板化版本: github.com/vlfeat/vlfeat/blob/master/vl/covdet.c#L1071 它需要尽可能精简和平均(这个 C 宏在这方面非常有效)。
0赞 wcochran 7/14/2023
我喜欢奖金的例子..我可以更确信编译器将优化到比较指令。
0赞 alfC 7/22/2023
@wcochran,我喜欢没有模板参数的接口的原因不是效率或控制。原因是,除了微不足道的情况外,具有默认参数的 IMO 接口往往马虎且扩展性较差。
0赞 wcochran 7/22/2023
这是效率是重中之重的情况之一——我绝对想尽我所能迫使编译器将比较代码归结为处理器指令——内联所有内容。
0赞 alfC 7/22/2023
@wcochran,我怀疑使用或不使用默认参数会有什么不同,但如果它能让你睡得更好......