1

I try to understand the docs for std::less, where the following example is given for the usage with a template.

#include <functional>
#include <iostream>
 
template <typename A, typename B, typename C = std::less<>>
bool fun(A a, B b, C cmp = C{})
{
    return cmp(a, b);
}
 
int main()
{
    std::cout
      << std::boolalpha
      << fun(1, 2)   << ' ' // true
      << fun(1.0, 1) << ' ' // false
      << fun(1, 2.0) << ' ' // true
      << std::less<int>{}(5, 5.6)    << ' ' // false: 5 < 5 (warn: implicit conversion)
      << std::less<double>{}(5, 5.6) << ' ' // true: 5.0 < 5.6
      << std::less<int>{}(5.6, 5.7)  << ' ' // false: 5 < 5 (warn: implicit conversion)
      << std::less{}(5, 5.6)         << ' ' // true: less<void>: 5.0 < 5.6
      << '\n';
}

Output:

true false true false true false true

My question is:

How is a std:: function used as a template argument?

And in this concrete case, why are there curly brackets {} behind the C cmp = C or in the call std::less<double>{}(5, 5.6)?

lpnorm
  • 459
  • 3
  • 10
  • 4
    `std::less` isn't a function, it's a [functor](https://stackoverflow.com/questions/356950/what-are-c-functors-and-their-uses) – NathanOliver Jul 11 '22 at 15:07
  • 3
    Classic way to write before uniform unitialisation: `std::less()(5, 5.6)` – a combination of calling the constructor (first pair of parentheses) and then, as `std::less` is callable (has an `operator()`) calling this newly constructed object (second pair of parentheses). – Aconcagua Jul 11 '22 at 15:09

3 Answers3

1

How is a std:: function used as a template argument?

It isn't a function. std::less is a class template with one type parameter (which has the default void). It has an operator() member function, which means instances can be used like functions.

why are there curly brackets {}?

To get a default-constructed instance of the class.

std::less<T> has a primary template that compares two Ts via <, but since C++14 std::less<void> is an explicit specialisation. It's operator() is itself a member template with two type parameters, approximately:

template<>
struct less<void>
{
    template<typename T, typename U>
    bool operator()(const T& lhs, const U& rhs)
    { return lhs < rhs;}
};

That is, it can compare things of different types, as long as there is an unambiguous < between them (which may involve implicit conversions).

Caleth
  • 52,200
  • 2
  • 44
  • 75
1

This demo-code suffers from so-called 'uniform initialisation' (broken by design, be careful with its use especially in template code!), classic way to write the same code before uniform initialisation is:

std::less<int>()(5, 5.6); // others analogously

std::less is a callable class, a so-called 'functor', which means it provides an operator() by which objects of the class can be called just like a function (to be precise: the operator is a member function that gets called, just the syntax for getting called actually is the same).

The code presented now first constructs such an object (calls the constructor; first pair of parentheses) while afterwards the so constructed object is called (the operator() with two arguments, second pair of parentheses).

Equivalent code could be:

std::less<int> li; // note that the parentheses need to be left out as you would
                   // declare a function then instead of creating an object;
                   // braces for uniform initialisation could remain
li(5, 5.6);

Note, though, that the life-time of the object li is now longer is longer (until leaving the current scope) than the one of the temporary (only current expression). In given case, there are no visible side effects, though, notably as std::less doesn't have any in its destructor.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • Thank you so much for your answer! So `std::less<>{ }`is the same as `std::less<>( )`? Ist there some default behaviour functors have when initialised uniformly? – lpnorm Jul 11 '22 at 15:38
  • 1
    @lpnorm depending on where they appear, mostly yes, but sometimes the one with `()` is a function declaration. – Caleth Jul 11 '22 at 16:02
  • @lpnorm There's no difference in *most* cases in between classic and uniform initialisation – issues can arise, though, on selecting the appropriate overload of multiple constructors. `{}` syntax prefers, if there is a suitable one, a `std::initializer_list` constructor over others, e.g. for `std::vector`: `std::vector{1, 7}` creates a vector with two elements `1` and `7`, while `std::vector(1, 7)` creates a vector with seven ones (another overload!). – Aconcagua Jul 11 '22 at 16:10
  • Now imagine the initialiser list overload would not have been there initially, then uniform would have called exactly the same constructor as classic; then adding this constructor later would break any code using uniform initialisation already before. You only are safe with classic initialisation: As shown already for seven elements or via `std::vector v({1, 7});` to exactly construct two elements via initialiser list. This gets especially relevant within templates where some types might come with the initialiser list constructor and others not! – Aconcagua Jul 11 '22 at 16:10
  • Note, too, that the case @Caleth points out is exactly why the parentheses need to be left out in the equivalent form in my answer! – Aconcagua Jul 11 '22 at 16:13
1

std::less is actually not a function, it is a struct, the interface of which may be represented in simplified form as follows.

template<class T>
struct less
{
    bool operator()(const T& t1, const T& t2)
    { return t1 < t2;}
};

In template parameter, as we see, C is actually a type(because std::less<> is a type). So everything makes sense.

Karen Baghdasaryan
  • 2,407
  • 6
  • 24