0

I'm working on this exercise 18.7 in C++ Primer Plus (6th edition):

enter image description here

and the code I gave was like this, using the lambda expression as required:

#include <iostream>
#include <array>
#include <algorithm>

const int Size = 5;

template <typename T>
void sum(std::array<double, Size> a, T& fp);

int main()
{
    double total = 0.0;

    std::array<double, Size> temp_c = {32.1, 34.3, 37.8, 35.2, 34.7};
    sum(temp_c, [&total](double w){ total += w; });
    std::cout << "total: " << total << std::endl;
    std::cin.get();
    return 0;
}

template <typename T>
void sum(std::array<double, Size> a, T& fp)
{
    for (auto pt = a.begin(); pt != a.end(); ++pt)
        fp(*pt);
}

After I compiled it in VSCode, an error came out: cannot bind non-const lvalue reference of type 'main(int, char**)::<lambda(double)>&' to an rvalue of type 'main(int, char**)::<lambda(double)>'. I also checked the official answer, which is virtually the same as my code and gave the same error while being compiled. I think this is because the void type function doesn't match the T& type called in template function sum, but how to modify the code while keeping the original sum() function unchanged as required in the problem? And I'm also confused why there's a lvalue & rvalue problem here.

Thanks in advance for any answers & explanations.

erpxyr2001
  • 151
  • 2
  • 6
  • 1
    Hint: if you substitute the typedef back into the error message, it reads like `cannot bind non-const lvalue reference of type 'T&' to an rvalue of type 'T'`. Is that more familiar? Have you tried, for example, declaring `foo(int& x)` and then calling `foo(1);`? See how the same problem occurs? – Karl Knechtel Jan 20 '23 at 09:02

2 Answers2

4

By defining your lambda inline you have made it a temporary, an r-value. This means that because the template takes the function pointer as a non-const ref (ugh!), there's no conversion - the rvalue cannot be bound to an l-value parameter, so it does not compile. If you lift the lambda out into its own variable, it works:

#include <iostream>
#include <array>
#include <algorithm>

const int Size = 5;

template <typename T>
void sum(std::array<double, Size> a, T& fp);

int main()
{
    double total = 0.0;

    std::array<double, Size> temp_c = {32.1, 34.3, 37.8, 35.2, 34.7};
    auto l = [&total](double w){ total += w; };
    sum(temp_c, l);
    std::cout << "total: " << total << std::endl;
    std::cin.get();
    return 0;
}

template <typename T>
void sum(std::array<double, Size> a, T& fp)
{
    for (auto pt = a.begin(); pt != a.end(); ++pt)
        fp(*pt);
}

And even though the assignment explicitly says not to adapt sum(), if you turn the fp parameter into a const ref, the original temporary variable will compile too - and that would be a much more canonical C++ way of implementing this.

Joris Timmermans
  • 10,814
  • 2
  • 49
  • 75
2

The problem is that you're passing a temporary (the lambda [&total](double w){ total += w; }) to a function that expects a non-const lvalue reference. Those cannot bind to temporaries such as the one you're passing here.

Writing

template <typename T>
void sum(std::array<double, Size> a, T fp)

should do the trick.

As an aside, it would be better to pass the array by const& to avoid copying:

template <typename T>
void sum(const std::array<double, Size>& a, T fp)

EDIT: Overlooked the part where it says sum should not be changed. In that case store the lambda in a variable before the call to sum and then pass it, as pointed out in Joris Timmermans' answer. This will provide you with the necessary lvalue that the reference can bind to.

Matthias Grün
  • 1,466
  • 1
  • 7
  • 12
  • 2
    Basically the same answer as mine, posted at basically the same time :-) Note though that the assignment says that sum cannot be modified, so the non-ideal solution here would be to assign the lambda to a variable. – Joris Timmermans Jan 20 '23 at 09:02
  • Yup! I'd overlooked the part where it says `sum` cannot be modified :( – Matthias Grün Jan 20 '23 at 10:47