问题描述
我想要一个接受 std::string
的函数和另一个接受不是 std::string
的任何东西的函数。
所以我有:
#include <iostream>
#include <string>
void foo(const std::string& str) {
std::cout << "string version called" << std::endl;
}
template<class T>
void foo(T&& str) {
std::cout << "template version called" << std::endl;
}
int main() {
foo(std::string{"hello"});
return 0;
}
问题在于调用的是模板化版本而不是 std::string
版本。
那么我怎样才能有一个 foo
函数,它可以接受任何东西,或者特别是一个 std::string
?
解决方法
模板化的 foo
使用转发引用,当传递临时 std::string
像 std::string{"hello"}
时,推导后函数参数 str
将是 std::string&&
,它比采用 foo
的非模板 const std::string&
更好的匹配。
您可以对模板参数施加限制,使其仅在传递非 std::string
时才可用。例如
template<class T>
std::enable_if_t<!std::is_same_v<std::decay_t<T>,std::string>>
foo(T&& str) {
std::cout << "template version called" << std::endl;
}
,
您的问题是您通过转发参考来获取。右值绑定到 std::string&&
比 std::string const&
更好,所以你的模板 ks 是首选。
改成
template<class T>
void foo(T const& str) {
std::cout << "template version called" << std::endl;
}
,
使用显式特化而不是重载函数。
template<>
void foo<std::string>(const std::string& str) {
...
我认为是对的;自由函数的显式特化不是 C++98 的原创,而是后来添加的。
(要正确仍然很痛苦,因为您的专业化必须与为 T
推导出的实际类型相匹配,如果不存在,则可能包括 const
和 &
在参数列表中;我已经将它有效地用于普通的值传递类型,例如特殊类型为 int
)
在 C++20 中,您可以使用 requires
子句使模板不适用于 std::string
。如果 requires
不可用,您可以使用 enable_if
做同样的事情。
您也可以只编写一个函数,但在主体中使用 constexpr if
来提供特殊情况。这完全避免了重载机制的参与,并让您可以编写确定特殊情况的确切规则。
更新:旧的和新的
在最初的模板规范中,函数模板不能被显式地特化,其想法是重载函数。你明白为什么这不能按预期工作:模板总是完全匹配,即使在调用特定函数时(使用简单的转换,添加 const
,通过引用传递),如果只重载非模板函数.
解决方法是制作一个虚拟的 class 模板,其中包含一个静态成员函数。因此,如果您最初有一个函数模板 f
并且您需要对其进行显式特化,请将函数体移动到:
template <typename T>
struct C {
void f (const T&) { /* body goes here */ }
};
现在您可以编写 C
的显式特化,因此 C::f
:
template<>
struct C<std::string> {
static void f (const std::string&) { /* special code goes here */ }
};
然后,为了保持与现有代码的兼容性,为仅调用 f
的(非成员)C<T>::f
编写一个新主体。
现在,今天这样做你会做得更好,并使用完美转发。
template <typename T>
void f (const T&& param)
{
C<T>::f(std::forward<T>(param));
}
现在,看看这与仅仅能够显式特化一个函数有何不同。模板参数推导在包装器调用上完成,然后T
的确定值用于类模板实例化,然后最终成员函数调用不进行任何推导而是将应用转换为正常函数调用。它没有“始终完美匹配”的行为。论证的确切形式可能会有所不同;例如无论您是传递值还是 const
引用。
事实上,由于完美转发,它保留了包装器调用的值类别,并且您实际上可以在显式特化之一内重载 f
!也就是说,您可以为右值、常量或非常量等使用单独的形式。
这里,包装函数是用 const
声明的,它失去了区分非常量参数的能力,但这使得模板参数推导不包括 const
变得容易。您可以添加自己的规范化步骤,将实际参数的类型转换为您想要的普通 T
。事实上,您可以添加任何您想要的元编程逻辑,例如识别基类和派生类,这是重载和模板时出现的另一个问题。
使用typeid()
template <class T>
void foo(T &&str)
{
if (typeid(str) == typeid(string))
{
std::cout << "string version called" << std::endl;
}else{
std::cout << "template version called" << std::endl;
}
}
int main() {
foo(std::string{"hello"});
foo("hello");
return 0;
}
,
您的代码的问题在于您正在创建一个临时变量 std::string{"Hello"}
,它是T&&
的完全匹配(优于{ {1}}),因此选择模板化版本。你可以
-
专门为
std::string const&
设计模板std::string
-
重载函数并停用
template<typename T> void foo(T t) { // Your implementation for other data types } // Template specialisation for strings template <> void foo<std::string>(std::string str) { // Your implementation for strings goes here }
、std::string
、std::string const&
、std::string&&
的模板 对于后者,您可以使用 {{3} } 结合...
一次性禁用所有这些版本。std::is_same
-
您可以比较
// Function overload for strings void foo(std::string const& str) { // Your implementation for strings goes here } // Template disabled for strings template<class T> typename std::enable_if<!std::is_same<std::decay<T>::type,std::string>::value>::type foo(T&& t) { // Your implementation for the other data types }
或在 C++17 中甚至更好,有一个typeid
可以与constexpr if
结合使用std::is_same<T>
此外,如果你想做类似的事情
template<class T>
void foo(T&& t) {
if constexpr (std::is_same_v<T,std::decay_t<std::string>>) {
// Your implementation for strings
} else {
// Your implementation for the other data types
}
}
工作也一样(所以不是foo("Hello");
)你可以更进一步std::decay<T>
type trait struct
foo(std::string{"Hello"})
在这种情况下,// Gets used for std::string as well as char*
void foo(std::string const& str) {
std::cout << "string version called" << std::endl;
}
// Disable template for any std::string and char*
template<class T>
typename std::enable_if<!std::is_same<typename std::decay<T>::type,std::string>::value &&
!std::is_same<typename std::decay<T>::type,char const *>::value &&
!std::is_same<typename std::decay<T>::type,char *>::value>::type
foo(T&& t) {
std::cout << "template version called" << std::endl;
}
也会调用重载的 foo("Hello")
版本。没有这个,没有 std::string
的任何调用都将转到模板版本!在这里试试also excluding any sort of char* C++11。