强制 C++ 优先使用带有隐式转换的重载而不是模板

Forcing C++ to prefer an overload with an implicit conversion over a template

我遇到这样一种情况,我需要重载解析来优先选择具有隐式转换的重载,而不是具有相同名称的模板函数。

考虑以下示例:

#include <iostream>
#include <functional>

void call_function(const std::function<void()>& function)
{
   std::cout << "CALL FUNCTION 1" << std::endl;
   function();
}

template <typename Function>
void call_function(const Function& function)
{
    std::cout << "CALL FUNCTION 2" << std::endl;
    function();
}

int main()
{
    // Pass a lambda to "call_function"
    // This lambda is implicitly convertible to 'std::function<void()>'
    // Even though it is implicitly convertible, the template function is selected by the compiler.
    call_function([]{
        std::cout << "TEST" << std::endl;
    });
}

输出:

CALL FUNCTION 2
TEST

不幸的是,编译器似乎检测到 call_function 的第一个实现需要隐式转换以将我传递给它的 lambda 转换为 std::function<void()> 对象,因此它确定模板版本更匹配并使用模板。我需要 强制 编译器更喜欢隐式转换重载而不是模板,因此输出将是:

CALL FUNCTION 1
TEST

我怎样才能做到这一点? (另请注意,我仅限于 C++11 兼容编译器,因此我无法使用 C++14 及更高版本的功能)

重载解析永远不会优先于隐式转换而不是精确匹配。由于模板将始终完全匹配,因此选择非模板的唯一方法是确保它也不需要任何转换。

为此,您可以先将闭包(lambda 表达式的结果)转换为正确的类型:

    call_function(static_cast<std::function<void()>>([]{
        std::cout << "TEST" << std::endl;
    }));

现在传递的正是第一个重载(“FUNCTION 1”)所采用的类型,因此这就是将要选择的类型。

就是说,如果您关心调用的是哪一个,您可能不应该使用重载。重载通常应保留在重载本质上等效的情况下,因此您真的不关心调用了哪个。

如果您想更改重载,以便在可能存在隐式转换时选择前一个重载,而后者作为备份,您可以通过 std::enable_if:[=17 使用 SFINAE 执行此操作=]

#include <type_traits>

void call_function(const std::function<void()>& function)
{
   std::cout << "CALL FUNCTION 1" << std::endl;
   function();
}

template <typename Function,
    // Consider this overload only if...
    typename std::enable_if<
        // the type cannot be converted to a std::function<void()>
        !std::is_convertible<const Function&, std::function<void()>>::value,
        int>::type = 0>
void call_function(const Function& function)
{
    std::cout << "CALL FUNCTION 2" << std::endl;
    function();
}

Demo


或者,如果您希望能够支持未知数量的 call_function 重载,并且 "CALL FUNCTION 2" 作为备份重载以防 none 函数工作,您也可以这样做,但需要做更多的工作:

// Rename the functions to `call_function_impl`
void call_function_impl(const std::function<void()>& function)
{
   std::cout << "CALL FUNCTION 1" << std::endl;
   function();
}

void call_function_impl(const std::function<void(int, int)>& function)
{
   std::cout << "CALL FUNCTION 2" << std::endl;
   function(1, 2);
}

// The backup function must have a distinct name
template <typename Function>
void call_function_backup_impl(const Function& function)
{
    std::cout << "CALL FUNCTION backup" << std::endl;
    function();
}


// Implement std::void_t from C++17
template <typename>
struct void_impl {
    using type = void;
};

template <typename T>
using void_t = typename void_impl<T>::type;

// Make a type trait to detect if the call_function_impl(...) call works
template <typename Function, typename = void>
struct has_call_function_impl
    : std::false_type
{};

template <typename Function>
struct has_call_function_impl<Function,
    void_t<decltype(call_function_impl(std::declval<const Function&>()))>>
    : std::true_type
{};


// If the call_function_impl(...) call works, use it
template <typename Function,
    typename std::enable_if<
        has_call_function_impl<Function>::value,
        int>::type = 0>
void call_function(const Function& function)
{
    call_function_impl(function);
}

// Otherwise, fall back to the backup implementation
template <typename Function,
    typename std::enable_if<
        !has_call_function_impl<Function>::value,
        int>::type = 0>
void call_function(const Function& function)
{
    call_function_backup_impl(function);
}

Demo