C++ SFINAE 解析顺序
C++ SFINAE Resolution Order
我有两个功能
template <typename... Args>
void foo(Args&&... args) { /* ... */ }
template <typename... Args>
void foo(const std::string& name, Args&&... args) { /* ... */ }
目前所有像 foo("bar", /* arguments */)
这样的调用都尝试转到第一个函数而不是第二个函数。我想重新排序这些功能,以便 SFINAE 在第一个之前找到第二个。我不能使用 std::enable_if
来检查字符 array/string,因为 Args...
包可能包含 std::string&
或 const char (&) []
。我该怎么做?
这里的问题是 "bar"
不是 std::string
。 void foo(const std::string& name, Args&&... args)
不会调用任何数量的重新排序,因为这需要转换,而 void foo(Args&&... args)
将产生完全匹配。
一种解决方法是使用 literal string operator 并使用 "bar"s
使 "bar"
成为字符串。这确实需要更改
template <typename... Args>
void foo(const std::string& name, Args&&... args) { /* ... */ }
进入
template <typename... Args>
void foo(std::string&& name, Args&&... args) { /* ... */ }
template <typename... Args>
void foo(std::string& name, Args&&... args) { /* ... */ }
因为 "bar"s
是纯右值并且会匹配您的主函数,因为这会推导出一个右值引用,它比 const 左值引用更受欢迎。
template <typename... Args>
void foo(Args&&... args) { /* ... */ }
正如您所发现的那样,问题在于此函数是贪婪的,并且几乎可以匹配您向它抛出的所有内容。编译器更喜欢另一个重载的唯一情况是参数类型完全匹配——如果需要任何类型的转换,那么编译器将更喜欢实例化第一个模板。
解决此问题的最通用方法是,如果第一个参数可以转换为 std::string
,则使用 SFINAE 禁用第一个重载,我们可以使用标准类型特征 [=13] 对其进行测试=].使用这种方法,一对合适的重载将是
// General case
template <typename First, typename... Rest,
std::enable_if_t<!std::is_convertible<First, std::string>::value, int> = 0>
void foo(First&& first, Rest&&... rest) { ... }
// First argument can be converted to string
template <typename... Args>
void foo(const std::string& first, Args&&... args) { ... }
我有两个功能
template <typename... Args>
void foo(Args&&... args) { /* ... */ }
template <typename... Args>
void foo(const std::string& name, Args&&... args) { /* ... */ }
目前所有像 foo("bar", /* arguments */)
这样的调用都尝试转到第一个函数而不是第二个函数。我想重新排序这些功能,以便 SFINAE 在第一个之前找到第二个。我不能使用 std::enable_if
来检查字符 array/string,因为 Args...
包可能包含 std::string&
或 const char (&) []
。我该怎么做?
这里的问题是 "bar"
不是 std::string
。 void foo(const std::string& name, Args&&... args)
不会调用任何数量的重新排序,因为这需要转换,而 void foo(Args&&... args)
将产生完全匹配。
一种解决方法是使用 literal string operator 并使用 "bar"s
使 "bar"
成为字符串。这确实需要更改
template <typename... Args>
void foo(const std::string& name, Args&&... args) { /* ... */ }
进入
template <typename... Args>
void foo(std::string&& name, Args&&... args) { /* ... */ }
template <typename... Args>
void foo(std::string& name, Args&&... args) { /* ... */ }
因为 "bar"s
是纯右值并且会匹配您的主函数,因为这会推导出一个右值引用,它比 const 左值引用更受欢迎。
template <typename... Args>
void foo(Args&&... args) { /* ... */ }
正如您所发现的那样,问题在于此函数是贪婪的,并且几乎可以匹配您向它抛出的所有内容。编译器更喜欢另一个重载的唯一情况是参数类型完全匹配——如果需要任何类型的转换,那么编译器将更喜欢实例化第一个模板。
解决此问题的最通用方法是,如果第一个参数可以转换为 std::string
,则使用 SFINAE 禁用第一个重载,我们可以使用标准类型特征 [=13] 对其进行测试=].使用这种方法,一对合适的重载将是
// General case
template <typename First, typename... Rest,
std::enable_if_t<!std::is_convertible<First, std::string>::value, int> = 0>
void foo(First&& first, Rest&&... rest) { ... }
// First argument can be converted to string
template <typename... Args>
void foo(const std::string& first, Args&&... args) { ... }