6
\$\begingroup\$

(A quick note on terminology: I use 'generalized functor' to refer to a functor that can be initialized from any callable entity, similar to the definition from Alexandrescu's Modern C++ Design. I use 'generic functor' to refer to functors in the general C++ sense, meaning anything which implements operator())

I'm working on a factory for generalized functors in C++11. The functor's interface looks like this:

// C-style function pointers, functors
template<typename call_type, typename res_type, typename... arg_types>
struct functor {
    call_type c;

    functor(call_type c) : c(c) {}

    res_type operator()(arg_types... args) { return c(args...); }
};

// Member function pointers
template<typename T, typename res_type, typename... arg_types>
struct functor<res_type(T::*)(arg_types...), res_type, arg_types...> {
    using call_type = res_type(T::*)(arg_types...);

    T t;
    call_type c;

    functor(T t, call_type c) : t(t), c(c) {}

    res_type operator()(arg_types... args) { return (t.*c)(args...); }
};

Please note that this is only a stub, and not part of the code I would like to be reviewed.

My attempted contribution is a factory method, make_functor, which should take care of deducing any template arguments where possible. In other cases (particularly overloaded functions) it should still allow users to specify the necessary template arguments directly. In practical terms, I would like it to satisfy the following testbed (also not part of the code I would like reviewed, though suggestions for extra testcases are always welcome):

bool test() {
    bool success = true;

    // C-style function
    auto f1 = make_functor(mult);
    success &= ( f1(2, 3) == 6 );

    // C-style function pointer
    int(*pf)(int, int) = mult;
    auto f2 = make_functor(pf);
    success &= ( f2(3, 3) == 9 );

    // Generic functor with overloaded operator()
    doubler d;
    auto f3 = make_functor<doubler, int, int>(d);
    success &= ( f3(2) == 4 );

    // Member function pointer
    container c;
    auto f4 = make_functor(c, &container::triple);
    success &= ( f4(4) == 12 );

    // Just making sure none of the above breaks the copy-ctor
    auto f5(f4);
    success &= ( f5(4) == 12 );

    return success;
}

What I've come up with so far is the following:

// C-style function pointers
template<typename res_type, typename... arg_types>
functor<res_type(*)(arg_types...), res_type, arg_types...> 
make_functor(res_type(*c)(arg_types...)) {
    return functor<res_type(*)(arg_types...), res_type, arg_types...>(c);
}

// Member function pointers
template<typename call_type, typename res_type, typename... arg_types>
functor<res_type(call_type::*)(arg_types...), res_type, arg_types...> 
make_functor(call_type c, res_type(call_type::*f)(arg_types...)) {
    return functor<res_type(call_type::*)(arg_types...), res_type, arg_types...>(c, f);
}

// Generic functor helper
template<typename call_type, typename res_type, typename... arg_types>
functor<call_type, res_type, arg_types...> 
make_functor_impl(call_type c, res_type(call_type::*)(arg_types...)) {
    return functor<call_type, res_type, arg_types...>(c);
}

// Generic functors
template<typename call_type>
auto make_functor(call_type c) -> decltype(make_functor_impl(c, &call_type::operator())) {
    return make_functor_impl(c, &call_type::operator());
}

// Overloaded generic functors
template<typename call_type, typename res_type, typename... arg_types>
functor<call_type, res_type, arg_types...> 
make_functor(call_type c) {
    return functor<call_type, res_type, arg_types...>(c);
}

This seems to work well with the above testbed, and a few other things I threw at it. But I still have a few concerns, and would appreciate it if someone could take a look at the following areas:

  • Maintainability. While the main goal is to shield the end-user from all the ugly, I'm still kinda hoping the factory itself might be made a little more readable.
  • References to temporaries. I think this is safe from dangling references, but I'm not 100% sure.

I am aware that the indentation in the code as posted is a little haphazard. This is just an attempt to keep things a little more readable here.

\$\endgroup\$
3
\$\begingroup\$

Although you say the functor code is only a stub, there are still a number of things to improve that I'll point out. If you're using C++11, then I'd absolutely remove the res_type template parameter, as this can be deduced using decltype in every case here. For example:

res_type operator()(arg_types... args) { return c(args...); }

could simply be:

auto operator()(arg_type... args) -> decltype(c(args...))
{
    return c(args...);
}

Further, the arguments here should be passed by (universal) reference, and then forwarded:

auto operator()(arg_type&&... args) -> decltype(c(args...))
{
    return c(std::forward<arg_type>(args)...);
}

The use of snake_case for template arguments is also highly unorthodox.

The final gripe is more of a question: what is this meant to be used for? If you already have a callable, why bother doing all this, instead of using it / passing it around directly. Between lambdas and std::function, I don't really see what this is adding.

\$\endgroup\$
  • \$\begingroup\$ Is 'this' in your last paragraph still referring to the functor itself? If so, the answer is 'peace of mind'. The original functor predates C++11. It's been updated a little (e.g. variadics instead of typelists), but suggestions to just rip it out and replace it with std::function have not been well received ;-) \$\endgroup\$ – Daan Dec 25 '14 at 17:59
  • \$\begingroup\$ It's not immediately obvious to me how I could avoid res_type in the second line of the member function pointer specialization (struct functor<...>). Would you mind expanding upon that part? \$\endgroup\$ – Daan Dec 25 '14 at 18:21
  • \$\begingroup\$ @Daan Yeah, my bad, somehow it didn't register that it was a template class specialization for functor, so you can't replace it as far as I can tell (which is a bit of a shame since having to specify the result type is a bit of a pain). Existing pre-C++11 makes more sense from a usage perspective. \$\endgroup\$ – Yuushi Dec 26 '14 at 5:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.