Passing a member function via std::function

2019-09-12 04:49发布

问题:

I want to check if a data field is valid (valid means being not null and not filled with the default value)

Basically

return (!connector->IsNull(field_id) and connector->Get<type>Default(field_id, default_value))

But "type" can be one of many types (string, int64, etc...) so there are 5-6 different functions. I made a helper function for it and I'm trying to pass in the relevant GetDefault...

template<typename T> bool IsValidField(std::unique_ptr<Connector>& connector, const std::function<T(int, T)> &GetDefault, int field_id, T default_value){
  return (!connector->IsNull(field_id) && connection->GetDefault(field_id, default_value) != default_value);
}

And I'm calling the helper function with....

IsValidField(connector, connector->GetStringWithDefault,20,"")

I get the error "error: reference to non-static member function must be called " because GetStringWithDefault isnt a static function, how do I fix this?

Alternately, is there a way of making it slightly less awkward?

回答1:

Two possible solutions, one is using std::bind and the other is using std::mem_fn.


For the solution using std::bind it could look something like

template<typename T, typename F>
bool IsValidField(std::unique_ptr<Connector>& connector, F GetDefault,
                  int field_id, T default_value)
{
    return (!connector->IsNull(field_id) &&
            GetDefault(field_id, default_value) != default_value);
}

Then call it like

IsValidField(

IsValidField(connector,
             std::bind(&ClassForConnector::GetStringWithDefault, _1, _2),
             20,"");

For the std::mem_fn solution, maybe something like

template<typename T, typename F>
bool IsValidField(std::unique_ptr<Connector>& connector, F GetDefault,
                  int field_id, T default_value)
{
    return (!connector->IsNull(field_id) &&
            GetDefault(connector, field_id, default_value) != default_value);
}

And call it like

IsValidField(connector,
             std::mem_fn(&ClassForConnector::GetStringWithDefault),
             20,"");


回答2:

Perhaps not the most neat, but the most trivial solution seems to be to wrap everything into lambda:

IsValidField(connector, [connector]() -> T {return connector->GetStringWithDefault(20, "")})

But then you need to adjust the IsValidField signature accordingly.



回答3:

There are two things going on here. Do one thing at a time.

Clean up how to call the GetStringWithDefault, so you can get something based off its type without knowing the name of the function, as follows:

namespace impl {
  template<class T, T Connector::*Method(int, T const&)>
  struct GetWithDefault {
    T operator()(Connector const& connector, int id, T const& default) const {
      return (connector.*Method)(id, default);
  };
}

template<class T>
struct GetWithDefault;

template<>struct GetWithDefault<std::string>:
  impl::GetWithDefault<std::string, &Connector::GetStringWithDefault>
{};

repeat for each supported type. Now, GetWithDefault<Bob>{}(*connector, id, default_bob) does what you want.

Now the null version:

template<class T>
bool IsValidField(Connector const& connector, int id, T const& default_value) {
    return !connector.IsNull(id) && (GetWithDefault<T>{}(connector, id, default_value)!=default_value);
  }
};

This moves the association between T and the gettor into a helper type GetWithDefault, where we implement it via explicit specialization (sort of like a traits class).