Как сделать две реализации шаблонной функции при помощи SFINAE? C++

Есть функция, которая принимает лямбду. Я через SFINAE проверяю ее на возможность вызова с определенными типами аргументов. Если это невозможно, то шаблон отбрасывается:

    template
    <
        typename Func,
        typename = typename std::enable_if_t<std::is_invocable_v<Func, Pixel<PIXEL_TYPE>&, const int[]>>
    >
    void for_each(cv::Mat& img, Func&& callback) const {
    
        // реализация

    }

Все прекрасно работает, но я хочу сделать еще вторую реализацию функции, которая принимает лямбду с аргументом не Pixel<PIXEL_TYPE>, а просто PIXEL_TYPE. Я попытался сделать так:

    template
    <
        typename Func,
        typename = typename std::enable_if_t<std::is_invocable_v<Func, PIXEL_TYPE&, const int[]>>
    >
    void for_each(cv::Mat& img, Func&& callback) const {

        // другая реализация

    }

Естественно мне выбило ошибку, что функция уже объявлена. Как мне реализовать подобный механизм и возможно ли это вообще?


Ответы (1 шт):

Автор решения: HolyBlackCat

Как вы заметили, шаблонные аргументы по умолчанию не учитываются при проверке функций на одинаковость.

Зато типы шаблонных параметров учитываются. Вместо typename = std::enable_if_t<...> используйте:

std::enable_if_t<..., std::nullptr_t> = nullptr

Также этот способ лучше тем, что проверку невозможно отключить, передав свой аргумент в этот параметр.

Тип здесь не обязан быть std::nullptr_t, можно взять какой-нибудь int. Но std::nullptr_t хорош тем, что у него только одно возможное значение, поэтому криворукий пользователь не сможет случайно инстанцировать ваш шаблон больше одного раза, передав разные аргументы в этот параметр.


В C++20 вместо SFINAE проще использовать requires:

template <typename Func> requires std::is_invocable_v<...>

Или, еще лучше, концепт std::invocable:

template <std::invocable<Pixel<PIXEL_TYPE>&, const int[]> Func>
→ Ссылка