C++: concatenate an enum to a std::string - c++

So I'm trying to concatenate an enum to an std::string. For this I wrote the following code.
typedef enum { NODATATYPE = -1,
DATATYPEINT,
DATATYPEVARCHAR
} DATATYPE;
inline std::string operator+(std::string str, const DATATYPE dt){
static std::map<DATATYPE, std::string> map;
if (map.size() == 0){
#define INSERT_ELEMENT(e) map[e] = #e
INSERT_ELEMENT(NODATATYPE);
INSERT_ELEMENT(DATATYPEINT);
INSERT_ELEMENT(DATATYPEVARCHAR);
#undef INSERT_ELEMENT
}
return str + map[dt];
}
and
DATATYPE dt1 = DATATYPEINT;
std::string msg = "illegal type for operation" + dt1;
I'm getting the following warning compiling this code.
warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: std::string msg = "illegal type for operation" + dt1; absyn.cpp:642:55: note: candidate 1: operator+(const char*, long int)
In file included from file.cpp:4:0: file.h:18:20: note: candidate 2: std::string operator+(std::string, DATATYPE) inline std::string operator+(std::string str, const DATATYPE dt){
What does this warning exactly mean, and how to solve it?

What you pass to the operator is a const char* (to a string literal) and a DATATYPE. Since there is no overload operator+(const char*, DATATYPE), the compiler looks for overloads where the parameters can be implicitly converted. The candidates are in the warning:
operator+(const char*, long int)
operator+(std::string, DATATYPE)
The first parameter can be converted from const char* to std::string or the second parameter can be converted from DATATYPE to long int. So the first overload "wins" the overload resolution on the basis of first parameter and the second overload "wins" on the basis of the second argument. Since there is no overload that "wins" the resolution on the basis of both arguments, they are ambiguous.
The compiler warns you because it suspects that it may have chosen different overload than what you meant to call. If you compile with -pedantic on gcc you'll get error: ambiguous overload for... instead of just a warning.
The solution is to disambiguate the call by passing parameters of types that match exactly. A simple way would be:
std::string msg = std::string("illegal type for operation") + dt1;
or nicer in c++14
std::string msg = "illegal type for operation"s + dt1;

Related

Error: "2 overloads have similar conversions"

EDIT: As I was writing the question I noticed that the method std::string GetNodeValue(const std::string& nodePath, const char * defaultValue) wasn't const. As LogicStuff also mentioned in his comment, adding the const qualification resolved the ambiguity.
I know this question was already been asked and answered properly here and several other times. I understand the underlying problem but I can't quite figure out why it is happening in this particular case and it has awoken my curious self.
I have the following class:
class ConfigurationReader
{
public:
// ...
std::string GetNodeValue(const std::string& nodePath, const char * defaultValue)
{
const std::string temp(defaultValue);
return GetNodeValue(nodePath, temp);
}
template <typename T> T GetNodeValue(const std::string & nodePath, T defaultValue) const
{
boost::optional<T> nodeValue = configuration.getNodeValueNothrow<T>(nodePath);
if ( nodeValue )
{
return *nodeValue;
}
LogConfigurationProblemsCri(logger, "Node not found: " << nodePath << ", Default value: " << defaultValue);
return defaultValue;
}
// ...
};
The template method has also several specializations for the types int16_t, uint16_t, and so on up to uint64_t.
It works like a charm when used:
string someValue = configurationReaderPtr->GetNodeValue("some_noe", "");
uint32_t otherValue = configurationReaderPtr->GetNodeValue("other_node", 11000);
bool yetAnother = configurationReaderPtr->GetNodeValue("other_node", true);
Except in one case:
uint32_t otherValue = configurationReaderPtr->GetNodeValue("other_node", 0);
The error I keep getting is:
"2 overloads have similar conversions
could be 'std::string ConfigurationReader::GetNodeValue(const std::string &,const char *)' or 'uint32_t ConfigurationReader::GetNodeValue(const std::string &,uint32_t) const'"
I tried casting the "default" value: uint32_t(0), static_cast<uint32_t>(0), 0U without any luck.
I should point out that I already found a workaround:
uint32_t otherValue = 0;
otherValue = configurationReaderPtr->GetNodeValue("other_node", otherValue);
But this doesn't answer my curiosity. I am currently using Microsoft Visual Studio 2012 Express and boost 1.54 libraries.
Any thoughts?
This is, because 0 is the literal for an empty pointer (which in modern C++ is replaced by "nullptr").
So 0 can be either an int or an empty pointer, especially a char*
Edit to add some reference:
You can find this in the standard as
4.10 Pointer conversions
A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t
(the last one is the reference to nullptr)
Both overloads are considered equally viable for
configurationReaderPtr->GetNodeValue("other_node", 0);
because:
requires an implicit conversion from 0, which is of type int to const char *.
requires an implicit conversion from ConfigurationReader* to ConfigurationReader const* (to call const-qualified member function)
After making both overloads (equally) const-qualified, the code compiles (the function template is preferred). The 1st overload also does not modify any members in the first place.
Live on Coliru
In your particular example, it seems prudent to change signature of string version to
std::string GetNodeValue(const std::string& nodePath, const std::string defaultValue)
This will remove any ambiguity.

Overload char pointers and string literals

I'm trying to create a class that will accept (and can disambiguate) string literals from char pointers. When searching this problem the solution provided was to create a 'holder' class that only accepts char pointers:
template< typename char_t >
class String
{
struct holder_t
{
const char_t* s;
holder_t( const char_t* s ) : s(s) {}
holder_t( char_t* s ) : s(s) {}
};
public:
// Construct from string literal
template< size_t S >
String( const char_t (&str)[S] )
{
std::cout << "String Literal: " << str;
}
// Construct from char pointer
String( holder_t h )
{
std::cout << "Char Pointer: " << h.s;
}
};
The big idea is that you should never have to explicitly create instances of this class, it should happen by implicit conversion:
void StringFoo( String<char> s )
{
}
However this results in compile errors for char pointers, why isn't the implicit conversion working?
int main()
{
// Works!
StringFoo( "literal" );
// error C2664: 'StringFoo' : cannot convert parameter 1 from 'const char *' to 'String<char_t>'
// with [ char_t=char ]
// No constructor could take the source type, or constructor overload resolution was ambiguous
StringFoo( (const char*)"const char ptr" );
// Works!
StringFoo( String<char>((const char*)"const char ptr") );
// error C2664: 'StringFoo' : cannot convert parameter 1 from 'char *' to 'String<char_t>'
// with [ char_t=char ]
// No constructor could take the source type, or constructor overload resolution was ambiguous
StringFoo( (char*)"char ptr" );
// Works!
StringFoo( String<char>((char*)"const char ptr") );
return 0;
}
This is just a simple pointless example; in my real code I created a class that will hold the hash of the string.
The hash should be generated at compile time if a string literal is passed and calculated at runtime if a char pointer is passed.
However this should be completely transparent to the user who just passes strings around, hashing is done automagically.
The reason the code doesn't work with pointer is that it requires two versions (char const* to holder and holder to String. However, implicit conversions will do at most one conversion.
For the purpose you are describing I'd think you can reasonably just distinguish between char const(&)[N] and char const*: since you always want to compute a hash anyway and just make it is a constexpr where possible, making the version taking char const(&)[N] a reference should do the trick.
To get from a string literal to a String requires two user-defined conversions - one to holder_t and another to String. But overload resolution only allows you to have one such conversion per argument.

Converting string to const* char

I have two string declarations:
killerName
victimName
I need to convert these two string values to const* char.
Example of how I use my method:
if (killer.IsRealPlayer) {
killerName = killer.GetName(); -- need to convert to const* char
victimName = victim.GetName(); -- need to convert to const* char
Notice(killerName + "has slain:" + victimName, killer.GetMapIndex(), false);
}
Some error I receive:
Error 111 error C2664: 'Notice' : cannot convert parameter 1 from 'std::basic_string<_Elem,_Traits,_Ax>' to 'const char */
It seems that function Notice have the first parameter of type const char * However the expression passed to it as the first argument
killerName + "has slain:" + victimName
has type std::string
Simply call the function the following way
Notice( ( killerName + "has slain:" + victimName ).c_str(), killer.GetMapIndex(), false);
Notice(string(killerName + "has slain:" + victimName).c_str(), killer.GetMapIndex(), false);
std::string::c_str() gives the const char* to the buffer. I think that's what you want.
See: http://www.cplusplus.com/reference/string/string/c_str/
As others already wrote, the result of killerName + "has slain:" + victimName is of type std::string. So, if your Notice() function expects a const char* as first parameter, you must convert from std::string to const char*, and since there is no implicit conversion defined for std::string, you must call the std::string::c_str() method:
Notice((killerName + "has slain:" + victimName).c_str(), killer.GetMapIndex(), false);
However, I'd like to ask: why do you have Notice() expecting a const char* as first parameter?
Would it be better to just use const std::string&? In general, in modern C++ code, you may want to use string classes like std::string instead of raw char* pointers.
(Another option would be to have two overloads of Notice(): one expecting a const std::string& as first parameter, and the other one expecting a const char*, if for some reason the const char* version does make sense in your particular context; this double overload pattern is used e.g. in the std::fstream constructor.)

why c++ accumulate third argument type cause compile failed

I write following codes in my editor,but it can't be compiled,it alerts:
cannot convert 'std::basic_string<char, std::char_traits<char>,
std::allocator<char> to 'const char*' in assignment|
||=== Build finished: 1 errors, 0 warnings ===|
Code:
#include <iostream>
//#inclide <algorithm>
#include <numeric>
#include <vector>
using namespace std;
int main()
{
std::vector<std::string> v;
v.push_back(string("a"));
v.push_back(string("b"));
v.push_back(string("c"));
string num = accumulate(v.begin(),v.end(),"");
std::cout << num;
return 0;
}
I don't know why it can't be compiled,please someone help me.Thanks:)
Paragraph 26.7.2/1 of the C++11 Standard specifies:
template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init);
[...]
1 Effects: Computes its result by initializing the accumulator acc with the initial value init and then
modifies it with acc = acc + *i [...] for every iterator i in the range
[first,last) in order.
[...]
String literals have type const char[], decaying to const char* when you pass them to functions. Therefore, the initializer you pass to accumulate() would be a const char*, and T would be a const char*.
This means acc from the expression above will be a const char*, and *i will be a string. Which means the following will not compile:
acc = acc + *i;
Because acc + *i yields a std::string, and on the left side of the assignment you have a const char*.
As other have suggested, you should do:
string num = accumulate(v.begin(),v.end(),string());
Also, you do not need to do:
v.push_back(string("a"));
When inserting strings into the vector. This is enough:
v.push_back("a");
An std::string will be implicitly constructed from the string literal "a".
One of the template parameters of std::accumulate is the return type, which would be deduced from the third function argument. This is also a type that should be capable of accumulating the values in the input iterator range. In your case, your return type should be std::string, but you are passing "", which is a const char[2]. This is not a type that can be copied and used for an accumulation.
You can fix this by passing an std::string:
string num = accumulate(v.begin(),v.end(), std::string());
Instead of "" as a third argument, explicitly call std::string():
string num = accumulate(v.begin(),v.end(),std::string());
The return type of std::accumulate is the same as the type of the third parameter, which is deduced to const char* in your case (because you're passing a string literal in).
This means that the function expects to work with const char*s internally, but the iterator range contains std::strings, so it barfs. That's why you must pass the correct type (std::string) in the third argument:
string num = accumulate(v.begin(), v.end(), std::string());

std::vector::operator[] doesn't test type

I have a struct like this:
struct VrtxPros{
long idx;
std::vector<std::string> pros;
VrtxPros(const long& _idx=-1, const std::string& val="") : idx(_idx)
{
if ( !val.empty() && val!="" )
pros.push_back(val);
}
};
and later in the code I use it like that:
long idx = 1234;
VrtxPros vp( 2134, std::string("-1") );
if ( margin ) vp.pros[0] = idx;
The compiler has no problem with that. I am wondering because the operator should deliver a reference.
I could not find an operator= in std::string which would accept a long as source.
Why does the code compile?
A std::string can be assigned to a char, and a long can be implicitly converted to a char, so a std::string can be assigned to a long. Your compiler will probably give a warning about this kind of implicit conversion (turn up the warning level and you'll see it, if you don't already).
See the #4 operator= listed here. Notice no constructor overload takes just a char, so this sort of thing can only be done for assignment.
For that matter, you can do this too:
std::string wow;
wow = 7ull; // implicit unsigned long long to char conversion
wow = 1.3f; // implicit float to char conversion
Use -Wconversion for g++ to get a warning for the implicit conversion from long to char.