我看到Simple的解决方案有两个问题:
(1) 编译这个测试用例失败
std::string hello = "hello";
const std::string earth = "earth";
Capitalize_And_Output(hello, "planet", earth);
因为earth
是 aconst std::string
并且没有可以接这个电话的重载。(试试看!)
(2) 对于可转换为 的类型(除了const char*
和类似的),它无法编译std::string
,例如,
struct beautiful {
operator std::string() const {
return "beautiful";
}
};
Capitalize_And_Output(hello, beautiful{}, "planet", earth);
以下实现解决了这些问题:
新解决方案:我的旧解决方案(如下)有效,但对char*
, char[N]
. 此外,它很复杂,并且使用了一些重载解析技巧来避免歧义。这个更简单,更有效。
void Capitalize_And_Output_impl(const char* str) {
while (char c = toupper(*str++))
std::cout << c;
}
void Capitalize_And_Output_impl(std::string& str) {
std::transform(str.begin(), str.end(), str.begin(), toupper);
std::cout << str;
}
void Capitalize_And_Output_impl(const std::string& str) {
Capitalize_And_Output_impl(str.data());
}
template<typename First>
void Capitalize_And_Output(First&& str) {
Capitalize_And_Output_impl(std::forward<First>(str));
std::cout << '\n';
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
Capitalize_And_Output_impl(std::forward<First>(str));
std::cout << ' ';
Capitalize_And_Output(std::forward<Strings>(rest)...);
}
因为我不使用std::transform
(第二次重载除外),所以不需要提前知道字符串的大小。因此,对于 achar*
没有必要调用std::strlen
(如在其他解决方案中)。
需要注意的一个小细节是这个实现只打印单词之间的空格。(它不会在最后一个单词之后打印一个。)
旧解决方案:
void Capitalize_And_Output_impl(std::string& str, int) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
std::cout << str << ' ';
}
void Capitalize_And_Output_impl(std::string str, long) {
Capitalize_And_Output_impl(str, 0);
}
void Capitalize_And_Output() {
std::cout << '\n';
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
Capitalize_And_Output_impl(std::forward<First>(str), 0);
Capitalize_And_Output(std::forward<Strings>(rest)...);
}
我想这两个Capitalize_And_Output_impl
重载值得解释。
首先考虑第二个参数(int
/ long
)。第一个重载可以采用const
在退出时大写的非左值(正如 Trevor Hickney 在对 Simple 的解决方案的评论中所要求的那样)。
第二个超载意味着接受其他所有内容,即右值和左值const
。这个想法是将参数复制到一个左值,然后将其传递给第一个重载。这个函数自然可以这样实现(仍然不考虑第二个参数):
void Capitalize_And_Output_impl(const std::string& str) {
std::string tmp(str);
Capitalize_And_Output_impl(tmp);
}
这项工作按要求进行。但是,Dave Abrahams 的一篇著名文章解释说,当您通过引用获取参数const
并将其复制到函数中时(如上所述),最好按值获取参数(因为在某些情况下,编译器可能会避免复制)。总之,这个实现是更可取的:
void Capitalize_And_Output_impl(std::string str) {
Capitalize_And_Output_impl(str);
}
不幸的是,对于第一个重载,Capitalize_And_Output_impl
对左值的调用也可以定向到这个重载。这会产生编译器抱怨的歧义。这就是为什么我们需要第二个参数。
第一个重载采用 a int
,第二个采用 a long
。因此,传递作为0
an 的字面量int
会使第一个重载优于第二个重载,但仅在出现歧义时才会如此。在其他情况下,即当第一个参数是右值或const
左值时,第一个重载不能使用,而第二个重载可以在文字0
被提升为之后使用long
。
最后两句。(1)如果你想避免递归调用Capitalize_And_Output
(我想这只是一个品味问题),那么你可以使用与 Simple 的解决方案相同的技巧(通过unpack
)和(2)我不认为需要::toupper
像 Simple 的解决方案一样传递 lambda 包装。