Search code examples
c++templateslambdasfinae

Take generic lambda return type into consideration for SFINAE


I'd like to have a function that takes a generic lambda, but I need slightly different logic based on the lambda's return type. For example, I'd like to do something like the following (this is a contrived example):

template<typename F>
void foo(F &&function) {
    auto result = function();
    // Have different behavior based on `result`'s type.
}

To facilitate this, I thought I could use SFINAE. In my mind, this would look something like:

template<typename F,
         typename std::enable_if_t<???>>
void foo(F &&function) {

}

Does anyone know how I can enable/disable a method based on the return type of F?


Solution

  • You don't need SFINAE for this. As mentioned in a comment, you could just write the different logic based on the return type in different overloads, and call that overload set

    void f(int); // do the logic for an int
    void f(double); // do the logic for a double
    // ... and so on
    
    template<typename F>
    void foo(F &&function) 
    {
        auto result = function();
        f(result); // get different behavior based on `result`'s type.
    }
    

    If the logic needs some data other than the return value, you'll need to pass that to the helper f.


    To avoid that, and have all the logic in one place, you could write a compile time "switch" on the type of the return value

    template<typename F>
    void foo(F &&function) 
    {
        auto result = function();
        if constexpr (std::is_same_v<decltype(result), int>)
          // logic for an int
        else if constexpr (std::is_same_v<decltype(result), double>)
          // logic for a double
        else
          // ... and so on
    }
    

    Note that these two options are not exactly equivalent, i.e. overload resolution may not behave exactly the same as is_same in all cases, due to factors like implicit conversions, etc. so you have to be a bit careful there. One fix would be to write an overload for the first version that catches everything other than exact conversions

    template<typename T>
    void f(T) = delete;
    

    and this will be much closer to the second version.