1

I'm trying to write a container that is able to categories objects and store pointers for which a categorisation function is true.

My problem is, that it does not compile and since I'm inexperienced in regard to callables like std::function or lambdas, I'm not sure how to fix it.

I want such a container, since I've the need to get some "Categories" often - and this makes it easy to cache the results. Especially if, in this example, the Dogs change their sound, the Categories can simply be recreated (since the callable is still there).

What is more, I'm interested in a solution that delivers good performance. As I read, std::functions are unlikely to be inlined. Is there a way to deliver inlined-performance?

The compiler says:

main.cpp: In function 'int main()':
main.cpp:51:108: error: no matching function for call to 'CategoryContainer<Dog>::addCategory(CategoryContainer<Dog>::Categories, main()::<lambda(auto:1&)>)'
     dogs.addCategory( CategoryContainer<Dog>::Categories::Wuff, [](auto& d){return d.makeSound()=="Wuff";} );

main.cpp:39:10: note: candidate: void CategoryContainer<T>::addCategory(CategoryContainer<T>::Categories, std::function<bool()>) [with T = Dog]
     void addCategory(Categories cat, std::function<bool()> f) {
main.cpp:39:10: note:   no known conversion for argument 2 from 'main()::<lambda(auto:1&)>' to 'std::function<bool()>'

main.cpp: In lambda function:
main.cpp:52:71: error: expected '{' before '(' token
     dogs.addCategory( CategoryContainer<Dog>::Categories::WauWau, []()(auto& d){return d.makeSound()=="WauWau";} );

main.cpp: In function 'int main()':
main.cpp:52:72: error: expected primary-expression before 'auto'
     dogs.addCategory( CategoryContainer<Dog>::Categories::WauWau, []()(auto& d){return d.makeSound()=="WauWau";} );

And here is my code:

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>

class Dog
{
public:
    std::string makeSound() { return _sound; }
    void setSound(std::string sound) { _sound=sound; }
private:
    std::string _sound = "Wuff";
};

template<class T>
class CategoryContainer
{
public:
    using objectContainer = std::vector<T>;
    using pointerContainer = std::vector<T*>;

    enum class Categories { Wuff, WauWau }; // Dogs are e.g. destinguished by the sound they make.

    struct Category {
        std::function<bool()> func;
        pointerContainer pointers;
        Category(std::function<bool()> f, objectContainer& data) : func(f) {
            for(auto& i : data)
                if( func(i) )
                    pointers.emplace_back(&i);
        }
    };

    CategoryContainer(size_t n) {
        data.resize(n); // Construct so many dogs.
    }

    void addCategory(Categories cat, std::function<bool()> f) {
        indexed[cat] = Category(f, data);
    }

private:
    objectContainer data;
    std::unordered_map<Categories, Category> indexed;
};

int main()
{
    CategoryContainer<Dog> dogs(10);
    dogs.addCategory( CategoryContainer<Dog>::Categories::Wuff, [](auto& d){return d.makeSound()=="Wuff";} );
    dogs.addCategory( CategoryContainer<Dog>::Categories::WauWau, []()(auto& d){return d.makeSound()=="WauWau";} );
}
max66
  • 65,235
  • 10
  • 71
  • 111
dani
  • 3,677
  • 4
  • 26
  • 60
  • An important point and still unanswered part is, how the compiler handles this situation. An addition to that point is very welcome. – dani Nov 26 '16 at 19:57

2 Answers2

2

You pass [](auto& d){return d.makeSound()=="Wuff";} as a functor but addCategory is declared as

void addCategory(Categories cat, std::function<bool()> f) {

So the functor is expected to take zero arguments but actually takes Instead of one.std::function<bool()> you should use std::function<bool(const T&)>

Alexey Guseynov
  • 5,116
  • 1
  • 19
  • 29
1

As pointed by Alexey Guseynov (+1), your func() in Category receive a T object.

So, as suggested, should be std::function<bool(T const &)>.

And you have to correct this in three points:

1) std::function<bool()> func; become std::function<bool(T const &)> func;

2) Category(std::function<bool()> f, objectContainer& data) : func(f) become Category(std::function<bool(T const &)> f, objectContainer& data) : func(f)

3) void addCategory(Categories cat, std::function<bool()> f) become void addCategory(Categories cat, std::function<bool(T const &)> f).

But isn't enough.

Now the makeSound() method of Dog is used with const Dog instantiations inside your lambda funtions. So makeSound() (that doesn't modify the object) should be modified in a const method

std::string makeSound() const { return _sound; }

At this point I have some errors because Dogs is incompatible with std::unordered_map because (if I understand correctly) there isn't a specialization for std::hash<CategoryContainer<Dog>::Categories>

But, to avoid this problem, if you can change the

std::unordered_map<Categories, Category> indexed;

in a ordered std::map (adding #include <map> too)

std::map<Categories, Category> indexed;

and if you change, in addCategory(), the row

indexed[cat] = Category(f, data);

that give error for reasons (involving constructors) that I don't want investigate further, with

indexed.emplace(std::piecewise_construct,
                std::forward_as_tuple(cat),
                std::forward_as_tuple(f, data));

you shoul'd be able to compile your example.

max66
  • 65,235
  • 10
  • 71
  • 111