jrs
jrs

Reputation: 153

Passing functions as arguments to avoid repeating code

So far I’ve been always coding in C++11 and I'm trying to understand how auto works in newer versions. In particular I have a two functions (f1 and f2 in the example) that work on a given struct. Both functions are almost the same but they change on what member they are operating, sometimes the operation is the inverse of one value, etc... (this example is an over simplification of the real program).

I want to avoid using conditionals and overloading functions to achieve this behaviour. Do you know of a cleaner, or more idiomatic, way to do this? Are there any problems in this code that I'm missing out?

typedef struct thing_t {
    double A;
    double B;
} THING;    

double get_A(const THING &t) {
    return t.A;
}

double get_B(const THING &t) {
    return t.B;
}

double convert(const THING &t, auto first, auto then) {
    return first(t) / then(t);
}

double f1(const THING &t) {
    return convert(t, get_A, get_B); 
}

double f2(const THING &t) {
    return convert(t, get_B, get_A);
}

int main() {
    THING t = {1.0, 2.0};
    std::cout << f1(t) << std::endl;
    std::cout << f2(t) << std::endl;
    return 0;
}

Thank you very much for taking the time to review my question.

Upvotes: 2

Views: 147

Answers (1)

Barry
Barry

Reputation: 303357

Firstly, you cannot take auto function parameters yet. That's non-standard C++. Also this typedef struct thing is a C-ism. In C++, just:

struct thing_t {
    double A;
    double B;
};

Now let's talk about generalizations. Does convert need to know about its argument? Maybe it itself is a higher-order function:

template <typename F, typename G>
auto convert(F f, G g) {
    return [=](auto const& x) { return f(x) / g(x); }
}

And then get_A and get_B just return members. We already have a syntax for that: pointers to member data (unfortunately they are not directly invocable so you need std::mem_fn):

double f1(const thing_t& t) {
    return convert(std::mem_fn(&thing_t::A), std::mem_fn(&thing_t::B))(t);
}

C++17 introduces std::invoke so you can make your utility function here more user-friendly. It is implementable in C++14 just fine, but it would let you write:

template <typename F, typename G>
auto convert(F f, G g) {
    return [=](auto const& x) { return std::invoke(f, x) / std::invoke(g, x); };
}

double f1(const thing_t& t) {
    return convert(&thing_t::A, &thing_t::B)(t);
}

double f2(const thing_t& t) {
    return convert(&thing_t::B, &thing_t::A)(t);
}

What do you think of that?

Upvotes: 6

Related Questions