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);
                ^
7
  • 1
    Since CMP is a type name you have to create an object first, e.g.: return CMP{}(a,b) ? a : b; Commented Jul 13, 2023 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. Commented Jul 13, 2023 at 22:09
  • A well-designed interface would not prevent the use of a stateful comparison predicate. template<typename CMP> float order(float a, float b, CMP cmp = {}) { return cmp(a,b) ? a : b; }, and it also give a more elegant implementation. Commented Jul 13, 2023 at 22:15
  • @alfC Would this still work if CMP had only explicit constructors? Commented Jul 13, 2023 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. godbolt.org/z/41GrcjnTG Commented Jul 13, 2023 at 22:24

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Do you think most C++ compilers will optimize the object construction away and just generate a simple comparison instruction?
@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.
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

2 Comments

Nice, but the nested template sort of scares me.
I wouldn't do this. Not all comparators are templates, I would be very annoyed if a library I use only accepted templated ones.
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);
}

5 Comments

My motivation is to use a C++ templated version of the C macro here: 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).
I like the bonus example .. I can be more confident the compiler will optimize down to a comparison instruction.
@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.
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, I doubt using or not using default arguments will make any difference, but if it lets you sleep better...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.