2

What is the proper C++ syntax to supply and use std::less as a template argument?

#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;
}

COMPILE ERROR:

$ 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);
                ^
JeJo
  • 30,635
  • 6
  • 49
  • 88
wcochran
  • 10,089
  • 6
  • 61
  • 69
  • 1
    Since `CMP` is a type name you have to create an object first, e.g.: `return CMP{}(a,b) ? a : b;` – UnholySheep Jul 13 '23 at 21:56
  • Reading the error message *no matching constructor* says the compiler's trying to use `CMP(a,b)` as a constructor rather than invoking the call operator. The answer and above comment explain why. – user4581301 Jul 13 '23 at 22:09
  • A well-designed interface would not prevent the use of a stateful comparison predicate. `template float order(float a, float b, CMP cmp = {}) { return cmp(a,b) ? a : b; }`, and it also give a more elegant implementation. – alfC Jul 13 '23 at 22:15
  • @alfC Would this still work if `CMP` had only explicit constructors? – Amolgorithm Jul 13 '23 at 22:18
  • @Amolgorithm, yes. But then you will need to pass a way to construct it. Which is correct. Anyway, your doubt is, I think, why most people hesitate to use it the idiom. https://godbolt.org/z/41GrcjnTG – alfC Jul 13 '23 at 22:24
  • @Amolgorithm, I misundertood your question. Yes, if the class as an explicit *default* constructor one needs something more verbose: `template float order(float a, float b, CMP cmp = CMP{}) ` https://godbolt.org/z/o5vnfT3n8 . But, how writes default constructor that are explicit ? – alfC Jul 13 '23 at 22:35
  • If you would like to be able to compare two values of different types (e.g `int` and `float`), without type casting occurring, then I would not recommend using `std::greater` and `std::less`. Instead, it would be a better idea to implement a less function and a greater function separately. Both of these functions would need to be able to support two different types, which could be done with two different parameters for the template. – Amolgorithm Jul 13 '23 at 23:02

3 Answers3

4

You can just return it like this:

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

This is because the constructor of std::less does not take any parameters.

CMP is a type, so you need to create an object of this type first, CMP{}, before you can invoke the () operator on the temporary CMP to do the comparison.

What the above solution does is: It instantiates an std::less object and then invokes operator() with the parameters a and b. This operator function then takes the two parameters and returns true if a is the lesser one (a boolean expresion). Then, via your ternary check, the smaller value between a and b is returned.

Amolgorithm
  • 697
  • 2
  • 20
  • Do you think most C++ compilers will optimize the object construction away and just generate a simple comparison instruction? – wcochran Jul 13 '23 at 22:28
  • @wcochran Not sure. If they are modern C++ compilers, they should be able to. I am only aware that MS Visual Studio will do this. I am not sure about GCC and Clang. – Amolgorithm Jul 13 '23 at 22:41
3

What is the proper C++ syntax to supply and use std::less as a template argument?

The std::less or std::greater are functors (i.e. function object). This means, prior to the operator() call, you need to default constructed them. Therefore, the return statement must be

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

However, if you want to save some typing, you might use template-template parameters for the order. Optionally, you can provide a template parameter(i.e typename T), to make it more generic (If intended) :

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

Now you call the order as

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
JeJo
  • 30,635
  • 6
  • 49
  • 88
2

A well-designed interface would not prevent the use of a stateful comparison predicate. And for free, you get a nicer implementation of the other alternatives.

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

NOTE 1:

Despite popular belief, if CMP is not default constructible, the syntax will still work; but of course, you will need to pass a way to construct it. Which is good.

test:

#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;
}

NOTE 2:

A better-designed interface will have a default order:

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

NOTE 3:

Technically, if the class has an explicit default constructor, one needs something more verbose for this to work, but I wouldn't implement this way because, if the default constructor is explicit, there must be a good reason for it, and we are basically overriding the intended behavior of the class designer.

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

Bonus:

Some people (including STL, https://en.cppreference.com/w/cpp/algorithm/sort) do not tolerate default parameters:

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);
}
alfC
  • 14,261
  • 4
  • 67
  • 118
  • My motivation is to use a C++ templated version of the C macro here: https://github.com/vlfeat/vlfeat/blob/master/vl/covdet.c#L1071 It needs to be as lean and mean as possible (This C macro is very effective on that end). – wcochran Jul 13 '23 at 22:37
  • I like the bonus example .. I can be more confident the compiler will optimize down to a comparison instruction. – wcochran Jul 13 '23 at 22:40
  • @wcochran, the reason I like interfaces without template parameters is not efficiency or control. The reason is that, except for trivial cases, IMO interfaces with default parameters tend to be sloppy and scale poorly. – alfC Jul 21 '23 at 20:13
  • This is one of those cases where efficiency is a high priority -- I definitely want to do all I can to force the compiler to boil the comparison code to just a processor instruction -- inlining everything. – wcochran Jul 21 '23 at 21:30
  • @wcochran, I doubt using or not using default arguments will make any difference, but if it lets you sleep better... – alfC Jul 22 '23 at 02:06