Errors when using C ++ function templates
This is my first post here. Feels uncomfortable in reality; -)
I am struggling with C ++ templates. I have a class 'textSettings"
that reads TXT files for value pairs, each one on a line, and puts them in a std :: map that uses a std :: string for key and value.
Possible inputs to a text file are, for example, GRID_SIZE 6
so that the map key becomes a string "GRID_SIZE"
and the value is a string "6"
.
In my program I want to use them as a type, in fact it's meaning: int/float/bool/string
.
The program only expects these 4 types to be entered as input. So I use a templated function to find a specific value in the map via my key and instantiate only those types.
In this function I check the type using std::tr1::is_same
to parse the value correctly.
It doesn't work in the else clause where Xcode complains, throwing three errors. It cannot use val for float or int or bool. All errors look the same:
/src/textSettings.cpp:82:17: Assigning 'int' from incompatible type 'std :: string' (aka 'basic_string') /src/textSettings.cpp:82:17: Assigning "float" from incompatible type 'std :: string '(aka' basic_string ') /src/textSettings.cpp:82:17: Assigning "bool" from incompatible type' std :: string '(aka' basic_string ')
However, all types of complaints are already being processed by other proposals if
. I really don't know how to deal with it. I am still learning C ++ and templates, so I might be ignoring something obvious. If I comment out the line, the program compiles and links fine. Obviously I cannot parse the values ββfrom the text file, then ...
I am using XCode 5.1.1
textSettings.h
template<typename T>
bool findValue(const std::string& key, T& val);
textSetting.cpp
template<typename T>
bool textSettings::findValue(const std::string &key, T& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
if (std::tr1::is_same<T, int>::value) {
val = atoi(it->second.c_str());
}
else if (std::tr1::is_same<T, float>::value) {
val = atof(it->second.c_str());
}
else if (std::tr1::is_same<T, bool>::value) {
val = static_cast<bool>(atoi(it->second.c_str()));
}
else if (std::tr1::is_same<T, std::string>::value) {
val = it->second; // <- ERROR HERE
}
else {
printf("Textsettings:: Error, type unknown!\n");
return false;
}
return true;
}
return false;
}
template bool textSettings::findValue<int>(const std::string&, int&);
template bool textSettings::findValue<float>(const std::string&, float&);
template bool textSettings::findValue<bool>(const std::string&, bool&);
template bool textSettings::findValue<std::string>(const std::string&, std::string&);
thanks for your comments
source to share
You are getting this error because for instances other than std::string
, you are assigning it->second
which is equal std::string
to the referenced variable val
that is not assigned for the string type ( float
, int
or bool
).
The fact that your condition std::tr1::is_same<T, std::string>::value
will evaluate as false
does not mean that you are not required to provide lexically correct code inside the conditional block.
This type of problem would be better addressed by creating a separate function overload for each required type and using an overloaded function call to automatically match the required:
bool ParseStr(const std::string& str, int& val) {
val = atoi(str.c_str());
return true;
}
bool ParseStr(const std::string& str, float& val) {
val = atof(str.c_str());
return true;
}
bool ParseStr(const std::string& str, bool& val) {
val = static_cast<bool>(atoi(str.c_str()));
return true;
}
bool ParseStr(const std::string& str, std::string& val) {
val = str;
return true;
}
template <typename T>
bool ParseStr(const std::string& str, T& val) {
printf("Textsettings:: Error, type unknown!\n");
return false;
}
template<typename T>
bool textSettings::findValue(const std::string &key, T& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
return ParseStr(it->second, val);
}
return false;
}
source to share
Problem
In each instance of a template, there is only one branch if
that is semantically correct and for which the compiler is able to generate proper instance code, while all the others are simply wrong.
Take a look at the following code, the error you are making is just the same:
{
int a = 33; // Assume this code was into a template and 'int = T'
if(std::is_same<decltype(a), int>::value)
a = 44; // Okay
else
a = "hello"; // How can this compile?
}
Since you are explicitly creating a templated function for all these types, the code is actually generated and thus you get errors.
Decision
There are several solutions, but specializing your function can be good (remember: functions cannot be partially specialized)
template<typename T>
bool textSettings::findValue(const std::string &key, T& val) {
// type not recognized, assert or handle
...
return false;
}
template<> // Specialized for int
bool textSettings::findValue<int>(const std::string &key, int& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
val = atoi(it->second.c_str());
return true;
}
return false;
}
// all the others..
template bool textSettings::findValue<int>(const std::string&, int&);
template bool textSettings::findValue<float>(const std::string&, float&);
template bool textSettings::findValue<bool>(const std::string&, bool&);
template bool textSettings::findValue<std::string>(const std::string&, std::string&);
source to share
I think you misunderstood the concept of using trait types. The type characteristics are evaluated as a constant expression at compile time and replaced with a constant value that is evaluated at run time. So in your example for T=int
the below equivalent template instance template
bool textSettings::findValue(const std::string &key, int& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
if (true) {
val = atoi(it->second.c_str());
}
else if (false) {
val = atof(it->second.c_str());
}
else if (false) {
val = static_cast<bool>(atoi(it->second.c_str()));
}
else if (false) {
val = it->second; // <- ERROR HERE
}
else {
printf("Textsettings:: Error, type unknown!\n");
return false;
}
return true;
}
return false;
}
As you can see, the error correcting string is still compiled, but on its own 'in the context of the type int
, its an invalid expression where the string is assigned to an integer.
type properties can be useful during template evaluation, but in this particular case, template specialization is best used to select code based on type.
template<typename T>
bool textSettings::findValue(const std::string &key, T& val) {
printf("Textsettings:: Error, type unknown!\n");
return false;
}
template<>
bool textSettings::findValue(const std::string &key, std::string& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
val = it->second; // <- ERROR HERE
return true;
}
return false;
}
template<>
bool textSettings::findValue(const std::string &key, int& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
val = atoi(it->second.c_str());
return true;
}
return false;
}
template<>
bool textSettings::findValue(const std::string &key, float& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
val = atof(it->second.c_str());
return true;
}
return false;
}
template<>
bool textSettings::findValue(const std::string &key, bool& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end()) {
val = static_cast<bool>(atoi(it->second.c_str()));
return true;
}
return false;
}
source to share
You need to use a solution based on what's called tagging . The most popular example is the std :: advance implementation. Here's an example. The problem with your code is that the compiler will try to instantiate your function for any given type T
. Imagine you are calling a function with T = int
. Then replace everything T
with int
. Will your function compile? No, because there val
will be int
, and you will try to assign std::string
to int
and there is no implicit conversion.
Here is your live code : All you need is the following trick (see the link for submitting tags below):
bool dispatcher(const std::string& str, int& val) {
val = atoi(str.c_str());
return true;
}
bool dispatcher(const std::string& str, float& val) {
val = atof(str.c_str());
return true;
}
bool dispatcher(const std::string& str, bool& val) {
val = static_cast<bool>(atoi(str.c_str()));
return true;
}
bool dispatcher(const std::string& str, std::string& val) {
val = str;
return true;
}
bool dispatcher(const std::string& str, ...) {
printf("Textsettings:: Error, type unknown!\n");
return false;
}
template<typename T>
bool findValue(const std::string &key, T& val) {
std::map<std::string, std::string>::iterator it;
it = data.find(key);
if (it != data.end())
return dispatcher(it->second, val);
return false;
}
source to share