Search code examples
c++templateslambdac++14enable-if

enable_if template param is lambda (with particular signature)


I have something this:

template<typename T>
class Image {
    Image(int w, int h, T defaultVal){
        for(int i=0; i<h; i++)
            for(int j=0; j<w; j++)
                pixel(j, i) = defaultVal;
    }
    template<typename F>
    Image(int w, int h, F initializer){
        for(int i=0; i<h; i++)
            for(int j=0; j<w; j++)
                pixel(j, i) = initializer(j, i);
    }
    // ...
};

My intention is to be able to instantiate an Image like this:

Image<int> img0(w, h, 0); // image of zeroes
Image<int> imgF(w, h, [](int j, int i){ // checkerboard image
    return (j/10+i/10) % 2;
});

But of course the second constructor signature will conflict with the first constructor signature. To resolve this conflict I want to restrict the second constructor's possible template instantiations.

I don't want to make it too complicated. Can you help me? My attempt:

template<typename F, typename = std::enable_if_t< // what now? how to check that F is callable (and if simple to check, with appropriate signature)

Solution

  • You're looking for std::is_invocable:

    template<typename F, typename = std::enable_if_t<
        std::is_invocable<F&, int, int>>
    

    F& because you're invoking it as an lvalue, and then after that it's just a list of types of parameters.


    The above requires C++17. It is implementable on C++14, but in your case we can also take a much simpler approach and just do:

    template <typename F, typename = decltype(std::declval<F&>()(1, 1))>
    

    F& for the same reason as above, and the rest of the expression is more familiar. Since we're calling with ints we don't care about the other things that INVOKE allows for (e.g. pointers to members).