AP11
AP11

Reputation: 617

How to pass lambda to function that requires class parameter

Edit: Compare was a given parameter class type, so I have created struct Compare, but i don't know how to cast the lambda variable passed in test to Compare class, which is a wanted parameter type.

Edit2: Alright guys, I think I sounded pretty unclear, so I'm posting the whole assignment text.

Create clamp function with two overloads

T const & clamp (T const & value, T const & low, T const & high)
T const & clamp (T const & value, T const & low, T const & high, Compare cmp)

which returns low if value is less than low, high if value is greater than high, and otherwise value. In other words, value is limited to the range [low, high]. The first overload assumes that T is comparable using the <operator, the second >overload then takes a suitable comparator as the 4th argument.

Unit test for the second overload is this:

TEST_CASE("Clamping using a custom lambda", "[all]") {
    const int low = 255;
    const int high = 0;
    auto cmp = [](int lhs, int rhs) { return std::abs(lhs) > std::abs(rhs); };
    int value, expected;
    std::tie(value, expected) = GENERATE(values<std::pair<int, int>>({
            {-1, -1},
            {256, 255},
            {-256, 255},
            {244, 244},
            {0, 0},
            {-22, -22},
            {-255, -255},
            {255, 255}
    }));
    REQUIRE(clamp(value, low, high, cmp) == expected);
}

Now, I am just making and submitting header file. And I don't know how to cast the 4th parameter, which is a lambda function, to a Compare that from assignment looks like a struct that I have to implement.

Remember, I cannot change the function declaration.

Upvotes: 1

Views: 1233

Answers (2)

Compare can a generic type. Then it won't matter what's passed in - as long as it's invokable, it'll work:

#include <functional>

template <tyepname T, class Compare>
T const & clamp (T const & value, T const & low, T const & high, Compare greater) {
  if (std::invoke(greater, value, high))) return high;
  if (std::invoke(greater, value, low))) return value;
  return low;
}

Also note that in C++ the argument names are not relevant in declarations and are ignored there, i.e. the declaration can use cryptic argument names, or even none at all. Now note that cmp here is not something that anyone should be teaching: it's impossible to tell at a glance what it's supposed to do. There's no need to write a comment about it. The name of the argument should be, obviously, greater. Hereby I apologize for all the times I wrote cmp instead of less or greater or equals - I'm sure some of this inertia has tainted my answers on SO. Mea culpa, and it was a stupid thing to do, I admit :)

So it's perfectly fine to have the declaration unchanged as per requirements, but still use argument names that don't make your blood boil in the definition:

// declaration
template <typename T, class Compare>
T const & clamp (T const & value, T const & low, T const & high, Compare cmp);

// definition
template <typename T, class Compare>
T const &clamp(T const &value, T const &low, T const &high, Compare greater);

I also find it rather confusing that the declarations offered in the assignment refer to some T, but it's not specified whether T is a constant type, or a generic type (i.e. a type parameter to a template).

You have to read the assignment carefully and divine whether T is something that is a constant type provided to you, but not specified in the assignment itself (i.e. a requirement imposed on you), or whether T means that your code must accept any type that will work in these circumstances. If that's not clear - ask the professor, because this sort of stuff is so fundamental that I'm baffled it wouldn't be clearly stated. My expectation is that you'd have included this ever-important detail were it provided in the assignment!

It is possible to "solve" the assignment in three circumstances that may arise - you need to determine what is expected of you, and if it takes you longer than 15 minutes to figure it all, absolutely, positively ask the instructor.

T and Compare are fixed types defined in test environment, but not provided a-priori

In other words, the test environment defines an arbitrary T and Compare, i.e. that they are fixed types with those names in global scope, then your functions won't be function templates - and in this case you do not care what T nor Compare is - they must be "well behaved" enough types to be usable, i.e. that a reasonable solution to the assignment exists. To write such code, make sensible assumptions to get it to compile, and then remove the definitions - since they'd clash with what the test environment provides.

// remove before running tests - this would be provided by test code
using T = int;
using Compare = bool(*)(T, T);
// end remove before running tests

T const & clamp (T const & value, T const & low, T const & high, Compare greater) {
  // your implementation here
}

T and Compare are meant to be fixed types determined from looking at test cases

For example, the test case you have shown implies that:

T = int, and Compare = bool(*)(int, int).

In that case, I'd still use the aliases T and Compare, but just set them to those determined types:

// do not remove those
using T = int;
using Compare = bool(*)(int, int);

T const & clamp (T const & value, T const & low, T const & high, Compare greater)  {
  // your code here
}

T and Compare do not name actual types, and are not constant

Then you need to make your functions generic - i.e. so that they also take the types as (template) arguments:

template <typename T, typename Compare>
T const & clamp (T const & value, T const & low, T const & high, Compare greater)
{ /* your code here */ }

In template as used above, the words typename and class are synonyms. But only as used above - that's not always the case.

Upvotes: 1

Remy Lebeau
Remy Lebeau

Reputation: 595622

There is nothing in the posted assignment that says Compare is expected to be a struct/class type that you have to implement.

There are only 3 ways to pass a lambda in a function parameter:

  • if the parameter is a plain function pointer, and the lambda is non-capturing.

  • if the parameter is defined as a template type.

  • if the parameter is defined as a std::function.

So, unless Compare in your case is defined as an alias for either int(*)(int,int) or std::function<int(int,int)>, then Compare should be a template parameter instead. That will allow the caller to decide what type it wants to use for the cmp callback. Compare should not be an actual class type of its own.

For example:

template<typename T, typename Compare>
T const& clamp(T const& value, T const& low, T const& high, Compare cmp)

This would allow the caller to pass in anything that clamp() can call using cmp(param1, param2) - which can include:

  • a plain function:
int compareInts(int lhs, int rhs) { return std::abs(lhs) > std::abs(rhs); }

REQUIRE(clamp(value, low, high, compareInts) == expected);
  • a std::bind()'ed class method:
class CompareInts {
public:
    int compare(int lhs, int rhs) { return std::abs(lhs) > std::abs(rhs); }
};

using namespace std::placeholders; 

CompareInts obj;
auto cmp = std::bind(&CompareInts::compare, &obj, _1, _2);

REQUIRE(clamp(value, low, high, cmp) == expected);
  • a functor implementing operator():
struct CompareInts {
    int operator()(int lhs, int rhs) { return std::abs(lhs) > std::abs(rhs); }
};

REQUIRE(clamp(value, low, high, CompareInts{}) == expected);
  • a lambda:
auto cmp = [](int lhs, int rhs) { return std::abs(lhs) > std::abs(rhs); };

REQUIRE(clamp(value, low, high, cmp) == expected);

Upvotes: 3

Related Questions