0

I am trying to create a function where callers can pass in lambdas with a certain set of known parameters (the return type can be anything the caller wants) Let's say the possible lambda signatures are

  1. T (Widget*)
  2. T (Widget2*)

I've tried a couple things but I can't get any of them to work

struct Widget2{
    int count = 0;
};

class Widget {
public:
template<typename Fn, typename T = std::invoke_result_t<Fn, Widget*>>
T doIt(Fn&& fn)
{
    return fn(_sibling);
}

template<typename Fn, typename T = std::invoke_result_t<Fn, Widget2*>>
T doIt(Fn&& fn)
{
    return fn(_other);
}

private: 
Widget* _sibling = nullptr;
Widget2* _other = nullptr;
};

I've also tried doing something like this

template<typename Fn>
auto doIt(Fn&& fn)
{
    if (std::is_invocable_v<Fn, Widget*>)
        return fn(_sibling);
    else if (std::is_invocable_v<Fn, Widget2*>)
        return fn(_other);
}

this code in a compiler: https://wandbox.org/permlink/wMAuw5XXnc0zTjlk

I'd rather avoid having to add multiple functions like doItWidget1, doItWidget2 and so on.

Is there a way I can basically overload functions based on the arguments of the incoming lambda?

1 Answer 1

1

You can use enable_if to sfinae away the wrong overload

template<typename Fn, std::enable_if_t<std::is_invocable_v<Fn, Widget*>, int> = 0>
auto doIt(Fn&& fn)
{
    return fn(_sibling);
}

template<typename Fn, std::enable_if_t<std::is_invocable_v<Fn, Widget2*>, int> = 0>
auto doIt(Fn&& fn)
{
    return fn(_other);
}

Note that you don't even need to figure out the return type T, you can just use auto to do that.

Here's a demo.

Sign up to request clarification or add additional context in comments.

2 Comments

Simpler: template<typename Fn> std::invoke_result_t<Fn, Widget*>> doIt(Fn&& fn), which triggers the SFINAE as well, but is simpler and isn't bypassable. Or template<typename Fn> auto doIt(Fn&&fn) -> decltype(fn(_sibling)) {return fn(_sibling);}, or template<typename Fn> auto doIt(Fn&&fn){return fn(_sibling);} depending on your compiler version
this is perfect. I was hoping the compiler would figure out the type if I specified it in the template explicitly. I'm curious as to why template<typename Fn, typename T = std::invoke_result_t<Fn, Widget*>> T doIt(Fn&& fn), fails, but replacing T with the same value works std::invoke_result_t<Fn, Widget*> doIt(...) works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.