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::stringvoid 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) { ... }

Corilu link