我有两个功能
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
检查char数组/字符串,因为Args...
包可能包含std::string&
或const char (&) []
。我该怎么做?
答案 0 :(得分:6)
此处的问题是"bar"
不是std::string
。调用void foo(const std::string& name, Args&&... args)
不会导致重新排序,因为这需要转换,而void foo(Args&&... args)
将产生完全匹配。
一种解决方法是,您可以使用literal string operator并使用"bar"
将"bar"s
设为字符串。这确实需要改变
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
是一个prvalue并且会匹配你的main函数,因为它会推导出一个rvalue引用,它优于const lvalue引用。
答案 1 :(得分:6)
template <typename... Args>
void foo(Args&&... args) { /* ... */ }
正如您所发现的那样,问题在于这个功能很贪婪,并且几乎可以匹配您投入的所有内容。编译器更喜欢另一个重载的唯一情况是,如果参数类型与完全匹配 - 如果需要任何类型的转换,则编译器将优先实例化第一个模板。
解决此问题的最常用方法是使用SFINAE来禁用第一个重载,如果第一个参数可以转换为std::string
,我们可以使用标准类型特征std::is_convertible
进行测试。使用这种方法,一对合适的过载将是
// 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) { ... }