强制 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();
}
或者,如果您希望能够支持未知数量的 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);
}
我遇到这样一种情况,我需要重载解析来优先选择具有隐式转换的重载,而不是具有相同名称的模板函数。
考虑以下示例:
#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();
}
或者,如果您希望能够支持未知数量的 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);
}