2

I have a class that works as a predicate to select value from list.

class Predicate {
public:
    // In this example, I am using QString as value type
    // this is not what happens in actual code, where more complex data is being validated
    virtual bool evaluate(const QString& val) const = 0;
};

Originally, I used lambda functions but this created lot of repetitive garbage code. So instead, I want to use predicate classes that use inheritance. For example:

class PredicateMaxLength: public RowPredicate {
public:
    PredicateMaxLength(const int max) : maxLength(max) {}
    virtual bool evaluate(const QString& val) const {return val.length()<maxLength;}
protected:
    const int maxLength;
};

To allow inheritance do it's deed, pointers are given rather than values:

class SomeDataObject {
    // Removes all values that satisfy the given predicate
    int removeValues(const std::shared_ptr<Predicate> pred);
}

Now we are surely stil going to use lambdas in cases where code would not be repetitive (eg. some special case). For this purpose, PredicateLambda has been created:

typedef std::function<bool(const QString& val)> StdPredicateLambda;
class PredicateLambda: public Predicate {
public:
    PredicateLambda(const StdPredicateLambda& lambda) : RowPredicate(), callback_(lambda) {}
    virtual bool evaluate(const QString& val) const override {return callback_(val);}
protected:
    const StdPredicateLambda callback_;
};

The nasty effect of this is that whenever lambda is used, it must be wrapped into PredicateLambda constructor:

myObject.deleteItems(std::make_shared<PredicateLambda>([]->bool{ ... lambda code ... }));

This is ugly. I have two options:

  • for every function that accepts predicate, have an overload that does the conversion seen above. This duplicates number of methods in header file
  • Have an implicit conversion from std::function<bool(const QString& val)> to std::shared_ptr<Predicate> which would execute this:

    std::shared_ptr<Predicate> magicImplicitConversion(const StdPredicateLambda& lambdaFn) {
        return std::make_shared<PredicateLambda>(lambdaFn);
    }
    

I came here to ask whether the second option is possible. If it is, does it carry any risk?

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Why use a `shared_ptr` instead of a `const &`? In all cases, if you can you should make `removeValues` a templated method. – Holt Aug 02 '16 at 12:29
  • @Holt I need inheritance to work properly. When passed by value, objects seem to lose overloaded functions, which is called [object slicing](http://stackoverflow.com/a/274634/607407). – Tomáš Zato Aug 02 '16 at 12:34
  • 1
    Yes, but a `const &` is not *pass by value*, it is *pass by reference*, and it works well with virtual function. But actually, you would have the same problem, see my answer for a real c++ solution. – Holt Aug 02 '16 at 12:35
  • Can you describe the "repetitive garbage code"? Just in the implementation of `evaluate`? – Yakk - Adam Nevraumont Aug 02 '16 at 13:51
  • @Holt Don't know how else to tell you, just wanted to say that it's a shame you deleted your answer. It wasn't exactly what I needed/wanted but someone else might find it useful. – Tomáš Zato Aug 02 '16 at 14:34

2 Answers2

3

If you don't want to use template to not expose code, you may use std::function:

class SomeDataObject {
    // Removes all values that satisfy the given predicate
    int removeValues(std::function<bool(const QString&)> pred);
};

and your predicate

class PredicateMaxLength {
public:
    explicit PredicateMaxLength(int max) : maxLength(max) {}
    bool operator ()(const QString& val) const {return val.length()<maxLength;}
protected:
    int maxLength;
};

So you can use either

SomeDataObject someDataObject;

someDataObject.removeValues(PredicateMaxLength(42));
someDataObject.removeValues([](const QString& s) { return s.size() < 42; });
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Does any object with `()` operator implicitly cast to `std::funtion`? Will virtual methods still work? Currently, I have `()` in topmost `Predicate` class implemented. – Tomáš Zato Aug 02 '16 at 12:45
  • Look at [std::function's constructors](http://en.cppreference.com/w/cpp/utility/functional/function/function): Functor should be callable with given argument. For the virtual part, you have to pass the functor via `std::ref` to avoid the slicing. – Jarod42 Aug 02 '16 at 12:50
  • @Jarod42 If you construct the predicate during the call (as you do), there is no slicing since the deduced type for `F` will be the type of the child directly. – Holt Aug 02 '16 at 12:51
  • I tried this but it refused to cast or construct the `std::function` from predicate: http://ideone.com/SpH8fI – Tomáš Zato Aug 02 '16 at 12:54
  • @TomášZato `Predicate` does not have `operator()` implemented. See http://ideone.com/2b4YYf. – Holt Aug 02 '16 at 12:54
  • @Holt Thanks, that was awkward mistake. Now it actually seems to work: http://ideone.com/SpH8fI Thanks for help. – Tomáš Zato Aug 02 '16 at 12:56
1

You want polymorphism, and you don't want to use template-style header lambdas. And you want to be able to have a few default cases.

The right answer is to throw out your Predicate class.

Use using Predicate = std::function<bool(const QString&)>;.

Next, note that your Predicate sub-types are basically factories (the constructor is a factory) for Predicates with some extra state.

For a std::function, such a factory is just a function returning a Predicate.

using Predicate = std::function<bool(const QString&)>;

Predicate PredicateMaxLength(int max) {
  return [max](QString const& str){ return val.length()<max; }
}

where the body of PredicateMaxLength goes in a cpp file.

If you have an insanely complicated set of state for your Predicate-derived class, simply give it an operator() and store it within a std::function. (In the extremely rare case that you have some state you should store in a shared ptr, just store it in a shared ptr).

A std::function<Signature> is a regular type that is polymorphic. It uses a technique known as type erasure to be both a value and polymorphic, but really you can call it magic.

It is the right type to use when you are passing around an object whose only job is to be invoked with some set of arguments and return some value.


To directly answer your question, no, you cannot define a conversion operator between a std::function and a std::shared_ptr<yourtype> without making your program ill formed, no diagnostic required.

Even if you could, a std::function is not a lambda, and a lambda is not a std::function. So your conversion operator wouldn't work.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524