I see two issues with Simple's solution:
(1) It fails to compile this test case
std::string hello = "hello";
const std::string earth = "earth";
Capitalize_And_Output(hello, "planet", earth);
because earth
is a const std::string
and there's no overload which can take this call. (Try it!)
(2) It fails to compile for types (other than const char*
and alikes) that are convertible to std::string
, for instance,
struct beautiful {
operator std::string() const {
return "beautiful";
}
};
Capitalize_And_Output(hello, beautiful{}, "planet", earth);
The following implementations solves these issues:
New solution: My old solution (below) works but it's not efficient for char*
, char[N]
. In addition, it's complicate and uses some overload resolution trickery to avoid ambiguities. This one is simpler and more efficient.
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)...);
}
Because I don't use std::transform
(except for the second overload), it doesn't need to know the size of the string in advance. Therefore, for a char*
there's no need to call std::strlen
(as in other solutions).
A small detail to notice is that this implementation only prints space between words. (It doesn't print one after the last word.)
Old solution:
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)...);
}
I guess the two Capitalize_And_Output_impl
overloads deserve explanations.
Firstly disconsider the second argument (int
/long
). The first overload can take non const
lvalues which are capitalized on exit (as requested by Trevor Hickney in a comment to Simple's solution ).
The second oveload is meant to take everything else i.e., rvalues and const
lvalues. The idea is to copy the argument to an lvalue which is then passed to the first overload. This function could naturally be implemented in this way (still disconsidering the second argument):
void Capitalize_And_Output_impl(const std::string& str) {
std::string tmp(str);
Capitalize_And_Output_impl(tmp);
}
This work as required. However, a famous article by Dave Abrahams explains that when you take an argument by reference to const
and copies it inside your function (as above), it's better to take the argument by value (because in some circunstances the compiler might avoid the copy). In summary, this implementation is preferable:
void Capitalize_And_Output_impl(std::string str) {
Capitalize_And_Output_impl(str);
}
Unfortunately, as for the first overload, calls to Capitalize_And_Output_impl
on lvalues can also be directed to this overload. This yields an ambiguity which the compiler complains about. That's why we need the second argument.
The first overload takes an int
and the second takes a long
. Therefore, passing the literal 0
, which is an int
, makes the first overload preferable over the second but only when the ambiguity arises. In the other cases, i.e., when the first argument is an rvalue or const
lvalue the first overload cannot be used whereas the second one can after the literal 0
is promoted to long
.
Two final remarks. (1) if you want to avoid the recursive call in Capitalize_And_Output
(I guess this is just a question of taste), then you can use the same trickery as in Simple's solution (through unpack
) and (2) I don't see the need to pass the lambda wrapping ::toupper
as in Simple's solution.