强制 C++ 更喜欢带有隐式转换的重载而不是模板

tjw*_*992 2 c++ templates overload-resolution implicit-conversion c++11

我有一种情况,我需要重载解析来更喜欢带有隐式转换的重载而不是同名的模板函数。

考虑以下示例:

#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;
    });
}
Run Code Online (Sandbox Code Playgroud)

输出:

CALL FUNCTION 2
TEST
Run Code Online (Sandbox Code Playgroud)

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

CALL FUNCTION 1
TEST
Run Code Online (Sandbox Code Playgroud)

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

Jer*_*fin 5

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

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

    call_function(static_cast<std::function<void()>>([]{
        std::cout << "TEST" << std::endl;
    }));
Run Code Online (Sandbox Code Playgroud)

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

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


Jus*_*tin 3

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

#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();
}
Run Code Online (Sandbox Code Playgroud)

演示


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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

演示