Reputation: 37834
I would like to write a variadic template function that accepts rvalues and lvalue references. It would capitalize std::strings, and display each parameter after they are capitalized. All lvalues should remain capitalized after the function has ended (i.e. have lvalues passed by reference).
For example, I would like this behaviour:
std::string hello = "hello";
std::string planet = "planet";
std::string earth = "earth";
//the order and amount of rvalues and lvalue references, should not matter
Capitalize_And_Output("hello","planet","earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,"planet","earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output("hello",planet,"earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output("hello","planet",earth); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,planet,"earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,"planet",earth); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output("hello",planet,earth); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,planet,earth); //outputs: "HELLO PLANET EARTH"
//lvalue references remain changed after the function call
std::cout << hello << std::endl; //outputs: "HELLO"
std::cout << planet << std::endl; //outputs: "PLANET"
std::cout << earth << std::endl; //outputs: "WORLD"
How can I get this above chunk of code to compile and work as shown?
So far, I am able to output the information, but I do not know how to handle the capitalization of the two different value types. The following code will compile, because I have commented out the lines that do not work.
#include <string>
#include <iostream>
#include <algorithm>
template<typename T>
void Capitalize_And_Output(T & str) {
//std::transform(str.begin(), str.end(), str.begin(), ::toupper); <- will not compile
std::cout << str<< std::endl;
return;
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First & str, const Strings&... rest) {
//std::transform(str.begin(), str.end(), str.begin(), ::toupper); <- will not compile
std::cout << str << " ";
Capitalize_And_Output(rest...);
return;
}
int main() {
std::string hello = "hello";
std::string planet = "planet";
std::string earth = "earth";
//the order and amount of rvalues and lvalue references, should not matter
Capitalize_And_Output("hello","planet","earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,"planet","earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output("hello",planet,"earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output("hello","planet",earth); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,planet,"earth"); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,"planet",earth); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output("hello",planet,earth); //outputs: "HELLO PLANET EARTH"
Capitalize_And_Output(hello,planet,earth); //outputs: "HELLO PLANET EARTH"
//lvalue references keep changed value after the function call
std::cout << hello << std::endl; //outputs: "HELLO"
std::cout << planet << std::endl; //outputs: "PLANET"
std::cout << earth << std::endl; //outputs: "WORLD"
return 0;
}
How can I get this to work?
Maybe the transform function doesn't work, because the rvalues are actually a different type? They are char*s?
What is going through my head:
Do I have to do something with type traits?
Something with R value references?
Something with universal reverences(not really sure what that is though)?
Please correct any misuse of the terminology!
Upvotes: 0
Views: 920
Reputation: 2485
First, the underlying reason why your program is not working is not only related to rvalue/lvalue compatibility. To show that consider this version that only pass the arguments by value
#include <string>
#include <iostream>
#include <algorithm>
template<typename T>
void Capitalize_And_Output(T str) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
std::cout << str<< std::endl;
return;
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First str, Strings... rest) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
std::cout << str << " ";
Capitalize_And_Output(rest...);
return;
}
In this case your main code will not work either. To show that consider the following
int main() {
std::string hello = "hello";
std::string planet = "planet";
std::string earth = "earth";
Capitalize_And_Output(std::string("hello"),planet,earth); // this will work
//Capitalize_And_Output("hello",planet,earth); // original code -> this will NOT work
return 0;
}
The line
Capitalize_And_Output("hello",planet,earth);
does not work because compiler consider the first argument "a const char" (char does not have .begin() and .end() iterators!)!. In fact, I get the following error message in g++
instantiated from here
teste.cpp:14:5: error: request for member ‘begin’ in ‘str’, which is of non-class type ‘const char*’
Now back to your original lvalue/rvalue question (collapsing rule says that && & = &. Here is the solution. You also cannot forget to "perfect forward" the rvalue references.
Then, a summary of the final answer is
template<typename T>
void Capitalize_And_Output(T&& str) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper); // <- will not compile
std::cout << str<< std::endl;
return;
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile
std::cout << str << " ";
Capitalize_And_Output(std::forward<Strings>(rest)...); // don't forget perfect forwarding.
return;
}
plus the fact that in main you must explicit use std::string constructor
Capitalize_And_Output(std::string("hello"),planet,earth); // this will work
EDIT: not sure how to handle this problem described in the standard.
Upvotes: 1
Reputation: 757
Read about Universal references. It's a much better way (IMHO) of thinking about these things and I feel his video explains rvalue refrences in general very well.
Upvotes: 1