0

I am trying to pass a lambda as parameter to a function that specifies if items should be included in a new vector.

At the moment I am using templates for the vector type (T) and the lambda expression (Func). But how can I make the function to take a lambda with T as parameter and bool as returning type?

This is my code actual:

#include <string>
#include <vector>

using namespace std;

// DTO with some members
struct DTO
{
    int Id;
    string Name;
    string Note;
};

// Function to receive a new vector, containing the searched items
template<typename T, typename Func>
vector<T> Where(vector<T> input, Func lambda)
{
    vector<T> v;
    for (auto it = input.begin(); it != input.end(); ++it)
        if (lambda(*it))
            v.push_back(*it);
    return v;
}

int main()
{
// Some test data
    DTO dto1;
    dto1.Id = 1;
    dto1.Name = "Test";
    dto1.Note = "asdasfa";
    DTO dto2;
    dto2.Id = 2;
    dto2.Name = "Test";
    dto2.Note = "asdasfa";
    DTO dto3;
    dto3.Id = 2;
    dto3.Name = "Test2";
    dto3.Note = "asdasfa";
    DTO dto4;
    dto4.Id = 2;
    dto4.Name = "Test2";
    dto4.Note = "asdasfa";
    DTO dto5;
    dto5.Id = 2;
    dto5.Name = "Test2";
    dto5.Note = "123";

    vector<DTO> numbers2 = 
    { 
        dto1,dto2,dto3,dto4,dto5
    };
// Get new vector with items where Name is Test and Note is asdasfa
    auto test = Where(numbers2, [](DTO dto) 
    {
        return dto.Name == "Test" &&
            dto.Note == "asdasfa";
    });
}

The goal is, to make it most comfortable, for the caller of the function.

2
  • 2
    What is wrong with your code? Your test object is a std::vector<DTO> with 2 entries in it when the program exits. One thing to improve it though: Pass the vectors and DTO's by const& instead of by value. Check here: coliru.stacked-crooked.com/a/e012296dc05980be Commented May 23, 2019 at 23:19
  • 2
    Sorry, I don't get it; this looks good. Is there a problem? If yes, you're going to have to actually tell us what it is! Commented May 23, 2019 at 23:20

3 Answers 3

2

In general, we try to stray from making templates necessitate a certain signature.

We prefer to:

  • document requirements, and
  • rely on compiler errors when something's not right.

As it is, your function template looks good, and your usage looks good, and that's why everything works.

Sure, you could pass something that returned not-bool that regardless worked in the if statement. And you could pass something that took not-DTO but worked anyway … but then so what? Just don't do those things. If it walks like a duck…

It's possible, with some machinery, to ensure that the callable takes specific arguments and returns a value of a specific type, but that would be complex and frankly not useful.

In short, your code is fine.

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

3 Comments

Yes, it was my intention, to show intellisense, which kind of lambda is expected and the error is shown befor compiling. So do you think it is enough to explan it in a comment over the function and to hope that the user understands the compiler error if something goes wrong? Is this users of an c++ library are familiar with?
Now I understand that it is already most comfortable for the caller and there is no need to change the code. I saw, that the function count_if is working similar.
@ChrisCrozz Yes that is enough and is the common proper C++ way. Maybe it would be "nicer" if things worked differently but then we'd be in another language :P
1

You can write your template like this:

template<typename T>
std::vector<T> Where(std::vector<T> input, std::function <bool (T)> f)
{
    std::vector<T> v;
    for (auto it = input.begin(); it != input.end(); ++it)
        if (f(*it))
            v.push_back(*it);
    return v;
}

But then you must invoke it like this:

auto test = Where <DTO> (numbers2, [](DTO dto) 
{
    ...
});

Live demo


Edit: If you only ever want to pass a non-capturing lambda, it is slightly more efficient to define your template like this:

template<typename T>
std::vector<T> Where(std::vector<T> input, bool (* f) (T))
...

But you still need the 'extra machinery' at the call site that Lightness Races in Orbit refers to (I think template deduction fails because the number of template parameters no longer matches the number of parameters passed to Where).

Live demo

All-in-all, stick with your original code.

5 Comments

It's better to take an arbitrary callable than a std::function (which packages a lambda unnecessarily)
In fact as you've found out, your suggestion requires more machinery at the callsite. Sorry, not seeing how this helps at all.
@LightnessRacesinOrbit Updated my answer, although, like you, I think the code posted in the question is fine as-is.
Yes this is working for me, but how you said there is the disadvantage that Where has to be called with DTO. But how was the compiler able to know T out of the context in my first implementation and here it has to be called explicit? So it is more typesafe but less comfortable for the user of the function. For what should I set a higher prority?
I explained in my answer why I believe template deduction fails when you do things this way. I agree with LightnessRacesinOrbit - stick with your original code. I have only left my answer here for posterity.
0

Try changing the template signature of Where to SFINAE out the Func types you don't want:

template<typename T,
         typename Func,
         std::enable_if_t<std::is_same_v<bool, std::invoke_result_t<Func, T>>>* = nullptr>
vector<T> Where(vector<T> input, Func lambda)

1 Comment

How I understand it, SFINAE is used to limit the used parameters for Func in this example, is this correct? But I am not able to call the function. Can you please show me how to do it?

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.