是否可以通过在函数参数包之前更改固定参数的数量来覆盖可变参数模板? 例如:
#include <iostream>
template <typename ...Args>
void foo(std::string, std::string, std::string, Args...)
{
std::cout << "THREE STRINGSn";
}
template <typename ...Args>
void foo(std::string, std::string, Args...)
{
std::cout << "TWO STRINGSn";
}
int main() {
foo("key", "msg", "data", 1);
}
运行此操作会导致调用第二个foo
,但我希望调用第一个。 有没有更好的方法来重载此功能?
选择第二个变体,因为它不涉及为最后一个参数创建std::string
实例所需的额外转换。如果您显式调用类构造函数(或调整参数以完全采用您传递的内容),那么它将正常工作:
foo(std::string{"key"}, std::string{"msg"}, std::string{"data"}, 1, 2); // THREE STRINGS
在线编译器
这是因为字符串文本不是类型std::string
。它属于const char[N]
型。因此,从第二个函数模板实例化的函数的第三个参数的类型被推导出为const char*
,这被认为比从第一个函数模板实例化的函数更匹配。
您可以将参数的类型从std::string
更改为const char*
,或使用VTT答案建议的显式转换。
正如 VTT 和 xskxzr 所解释的那样,"data"
是一个字符串文字,const char [5]
也是如此,所以可以转换为但不完全是std::string
,所以泛型模板类型比std::string
更匹配,编译器更喜欢第一个版本。
您可以选择传递std::string{"data"}
的第一个版本foo()
但是,如果您不想更改呼叫,另一种可能的解决方案是SFINAE启用/禁用foo()
的第二个版本。
我的意思是。。。如果您编写fIsCToS
(因为"第一个可转换为字符串")自定义类型特征
template <typename...> // for empty `Args...` list case
struct fIsCToS : std::false_type
{ };
template <typename T0, typename ... Ts>
struct fIsCToS<T0, Ts...> : std::is_convertible<T0, std::string>
{ };
您可以使用它重写foo()
的第二个版本,如下所示
template <typename ...Args>
typename std::enable_if<false == fIsCToS<Args...>{}>::type
foo(std::string, std::string, Args...)
{ std::cout << "TWO STRINGSn"; }
以下是您修改后的示例
#include <iostream>
template <typename...>
struct fIsCToS : std::false_type
{ };
template <typename T0, typename ... Ts>
struct fIsCToS<T0, Ts...> : std::is_convertible<T0, std::string>
{ };
template <typename ...Args>
void foo(std::string, std::string, std::string, Args...)
{ std::cout << "THREE STRINGSn"; }
template <typename ...Args>
typename std::enable_if<false == fIsCToS<Args...>{}>::type
foo(std::string, std::string, Args...)
{ std::cout << "TWO STRINGSn"; }
int main ()
{
foo("key", "msg", "data", 1, 2); // now print THREE STRINGS
}
如果可以使用 C++14,则可以使用std::enable_if_t
而不是typename std::enable_it<...>::type
,以便第二个foo()
可以简化为
template <typename ...Args>
std::enable_if_t<false == fIsCToS<Args...>{}>
foo(std::string, std::string, Args...)
{ std::cout << "TWO STRINGSn"; }
好的,所以我想出了一个不同的解决方案。 我讨厌回答我自己的问题,但这是我一直在寻找的答案,也许它可以帮助其他人。
相反,我所做的是不要重载模板化函数,而是将整个签名合并到单个参数包中。 然后,我重载了一个执行实际工作的非模板化函数,使用std::forward
将参数转发到这些重载函数之一。 通过这种方式,我让编译器决定调用哪个方法。
我的解决方案:
#include <iostream>
void print_call(std::string key, std::string msg, std::string data, int)
{
std::cout << "THREE STRINGSn";
}
void print_call(std::string key, std::string msg, int)
{
std::cout << "TWO STRINGSn";
}
template <typename ...Args>
void foo(Args &&...args)
{
print_call(std::forward<Args>(args)...);
}
int main() {
foo("key", "msg", 1);
foo("key", "msg", "data", 1);
}
在这里看到它运行: