1

Consider the template function sort(...) below. This is a wrapper function around std::sort. The purpose is to provide a better syntax when sorting vectors of user-defined classes. The first argument is the vector to be sorted. The second argument is a function specifying how the vector is to be sorted (on which public member variable or function).

std::sort require a compare-function in order to rank the different items in the vector. This is declared as a lambda inside my sort-function. However, this code only compiles if the lambda is declared 'auto', not if it is declared as bool. I find this strange. Could someone please explain?

(The code should compile as it stands now. To observe the problem, uncomment the line beginning with 'bool').

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

// Copy function is needed in sort(...), below
template<typename T>
auto copy(const std::vector<T>& v)
{
    auto r = std::vector<T>();
    r.reserve(v.size());

    for(const auto & e : v) {
        r.push_back(e);
    }

    return std::move(r);
}

template<typename T, typename F>
auto sort(const std::vector<T>& v, const F& f)
{
    auto r = copy(v);

    // ///////////// Consider the following line: /////////////////
    // bool compare = [=](const T& lhs, const T& rhs)               // does not compile, why?
    // auto compare = [=](const T& lhs, const T& rhs) -> bool    // compiles
    auto compare = [=](const T& lhs, const T& rhs)            // compiles
    {
        return f(lhs) < f(rhs);
    };

    std::sort(r.begin(), r.end(), compare);
    return std::move(r);
}

struct Foo {
    int id;
    string message;
};

int main()
{
   auto unordered = vector<Foo>();
   unordered.push_back(Foo{2, "the last element"});
   unordered.push_back(Foo{1, "the first element"});

   // I want to sort the vector by 'id'.
   auto ordered = sort(unordered, [](const auto& e) { return e.id; }); 

   cout << "Ordered vector (by id):" << endl;
   for(const auto& e : ordered) {
        cout << e.id << ", " << e.message << endl;
   }

   return 0;
}
unique_ptr
  • 233
  • 3
  • 9
  • 2
    It's because `compare` is a lambda, so it has the type of a lambda (which is unspecified), not of its return type. On this subject, this is interesting: https://stackoverflow.com/questions/7951377/what-is-the-type-of-lambda-when-deduced-with-auto-in-c11 – Olivier Sohn Jul 08 '18 at 18:58
  • For comparison: `bool f(); bool x = &f;` won't compile either (you'd need `bool(*x)() = &f;`, or, guess, `auto`). – Aconcagua Jul 08 '18 at 19:01
  • Ok, so declaring lambdas with anything but 'auto' does not make sense in general? – unique_ptr Jul 08 '18 at 19:03
  • 1
    I think it doesn't, except if you want to store the lambda in a std::function, for example: `std::function f = [](){};` – Olivier Sohn Jul 08 '18 at 19:07
  • The type of a lambda is implementation-defined (and, more than likely, non-denotable). However, as Olivier has noted, you can declare a lambda's type to be something like `std::function`, which will incur an implicit cast to that type. – Silvio Mayolo Jul 08 '18 at 19:08
  • Not 100% sure - wouldn't the std::function rather *store* the lambda inside? Assume better example is assignment to function pointer (which, of course, is only possible if capture is empty)? – Aconcagua Jul 08 '18 at 19:10
  • You don't have to use auto. You can implement the function without storing it in a variable. Something like `std::sort(r.begin(), r.end(), [=](const T& lhs, const T& rhs) { return f(lhs) < f(rhs); });` – Sid S Jul 08 '18 at 19:27
  • @SidS Hm, function parameters are not different from variables with local storage duration apart from being assigned before the function executes... – Aconcagua Jul 08 '18 at 20:57
  • @Aconcagua, my point was that you don't have to explicitly use `auto`. – Sid S Jul 08 '18 at 23:22

1 Answers1

3

The type of a lambda is implementation defined. Because of this if you are declaring a variable to hold a lambda, it must always be of type auto. You appear to be confusing the return type of the lambda with the type of the variable that actually holds the lambda itself.

// variable type | variable identifier | lambda expression (return type deduced)
   bool            compare             = [=](const T& lhs, const T& rhs)

// variable type | variable identifier | lambda expression              | return type
   auto            compare             = [=](const T& lhs, const T& rhs) -> bool  

// variable type | variable identifier | lambda expression (return type deduced)
   auto            compare             = [=](const T& lhs, const T& rhs) 

The above table illustrates what each part of the declaration relates to. In your code compare is a lamda. If you declare a variable of type bool and then try to assign a lamda to it you will inevitably get compiler errors.

Fibbs
  • 1,350
  • 1
  • 13
  • 23
  • "Always" is too strong. Read the comment section above, you'll find two ways described that don't use `auto`. – Sid S Jul 08 '18 at 19:36
  • Always is correct if you're declaring a variable rather than a temporary. I'll edit to be more clear. – Fibbs Jul 08 '18 at 19:38
  • You missed the `std::function` variable. – Sid S Jul 08 '18 at 19:39
  • 2
    `std::function` is a completely different type. That's like saying sometimes `int` can be declared as `std::vector`. – Fibbs Jul 08 '18 at 19:43
  • Did you see @OlivierSohn's example in the comments above ? – Sid S Jul 08 '18 at 19:45
  • Yes, `std::function` is just a wrapper class with a copy constructor that accepts lamdas. – Fibbs Jul 08 '18 at 19:48
  • Is the "wrapper class" variable a "variable holding a lambda" ? – Sid S Jul 08 '18 at 19:50
  • 3
    No. It is a class with a member that points to the lambda (possibly). It's implementation defined so it's hard to say. The point is `std::function` is not a lambda. A variable of type `std::function` does not hold a lambda any more than a variable of type `std::vector` actually holds an `int`. – Fibbs Jul 08 '18 at 19:58
  • Not 100% sure if covered by the standard (think so), GCC's implementation of std::function holds a *copy* of the object passed to. – Aconcagua Jul 08 '18 at 20:50