I am trying to write a C++ class that has some overloaded methods:
class Output
{
public:
static void Print(bool value)
{
std::cout << value ? "True" : "False";
}
static void Print(std::string value)
{
std::cout << value;
}
};
Now lets say I call the method as follows:
Output::Print("Hello World");
this is the result
True
So, why, when I have defined that the method can accept boolean and string, does it use the boolean overload when I pass in a non-boolean value?
EDIT: I come from a C#/Java environment, so quite new to C++!
"Hello World" is a string literal of type "array of 12 const char" which can be converted to a "pointer to const char" which can in turn be converted to a bool. That's precisely what is happening. The compiler prefers this to using std::string's conversion constructor.
A conversion sequence involving a conversion constructor is known as a user-defined conversion sequence. The conversion from "Hello World" to a bool is a standard conversion sequence. The standard states that a standard conversion sequence is always better than a user-defined conversion sequence (§13.3.3.2/2):
a standard conversion sequence (13.3.3.1.1) is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence
This "better conversion sequence" analysis is done for each argument of each viable function (and you only have one argument) and the better function is chosen by overload resolution.
If you want to make sure the std::string version is called, you need to give it an std::string:
Output::Print(std::string("Hello World"));
Not sure why nobody posted this, but you can add another overload that converts from const char* to std::string for you. This saves the caller from having to worry about this.
class Output
{
public:
static void Print(bool value)
{
std::cout << value ? "True" : "False";
}
static void Print(std::string value)
{
std::cout << value;
}
// Just add the override that cast to std::string
static void Print(const char* value)
{
Output::Print(std::string(value));
}
};
FWIW, it can be addressed this way (if templates can be used), if you don't want to add overloads for const char*.
#include <iostream>
#include <string>
#include <type_traits>
template <typename Bool,
typename T = std::enable_if_t<std::is_same<Bool, bool>{}>>
void foo(Bool)
{
std::cerr << "bool\n";
}
void foo(const std::string&)
{
std::cerr << "string\n";
}
int main()
{
foo("bar");
foo(false);
}
Since C++14 we have the operator""s from the std::string_literals namespace, which can be used to tell the compiler to bind to the string (or string_view in C++17) overload:
using namespace std::string_literals;
Output::Print("Hello World"s);
Prints: Hello World
Related
I've got a struct like this:
// Literal.hpp
struct Literal
{
std::variant<
std::nullptr_t,
std::string,
double,
bool
>
value;
friend std::ostream &operator<<(std::ostream &os, Literal &literal);
};
and I'm trying to implement the << operator like this:
// Literal.cpp
Literal::Literal() : value(value) {}
std::ostream &operator<<(std::ostream &os, const Literal &literal)
{
std::visit(/* I don't know what to put here!*/, literal.value);
}
I've tried implementing the operator like this (note: I would take any elegant solution it doesn't have to be a solution to this implementation below)
// In Literal.cpp
std::ostream &operator<<(std::ostream &out, const Literal literal)
{
std::visit(ToString(), literal.value);
return out;
}
struct ToString; // this declaration is in literal.hpp
void ToString::operator()(const std::nullptr_t &literalValue){std::cout << "null";}
void ToString::operator()(const char &literalValue){std::cout << std::string(literalValue);}
void ToString::operator()(const std::string &literalValue){std::cout << literalValue;}
void ToString::operator()(const double &literalValue){std::cout << literalValue;}
void ToString::operator()(const bool &literalValue){std::cout << literalValue;}
But in my main function, passing a char array literal doesn't casts it into a bool when it runs! ignoring the operator overload taking a char:
main() {
Literal myLiteral;
myLiteral.value = "Hello World";
std::cout << myLiteral << std::endl;
}
This is a bug in your standard library. Presumably you're using libstc++ (the GNU C++ standard library), since that's what Godbolt shows as messing up. If you compile with libc++ (Clang/LLVM's C++ standard library), this works as expected. According to std::vector<Types...>::operator=(T&& t)'s cppreference page, it
Determines the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types... in scope at the same time, except that:
An overload F(T_i) is only considered if the declaration T_i x[] = { std::forward<T>(t) }; is valid for some invented variable x;
If T_i is (possibly cv-qualified) bool, F(T_i) is only considered if std:remove_cvref_t<T> is also bool.
That last clause is there for this very situation. Because lots of things can convert to bool, but we don't usually intend this conversion, that clause causes conversion sequences that would not normally be selected to be selected (char const* to bool is a standard conversion, but to std::string is "user-defined", which is normally considered "worse"). Your code should set value to its std::string alternative, but your library's implementation of std::variant is broken. There's probably an issue ticket already opened, but if there isn't, this is grounds to open one. If you're stuck with your library, explicitly marking the literal as a std::string should work:
literal.value = std::string("Hello World");
For the elegance question, use an abbreviated template lambda.
std::ostream &operator<<(std::ostream &os, Literal const &literal)
{
std::visit([](auto v) { std::cout << v; }, literal.value);
// or
std::visit([](auto const &v) {
// gets template param vvvvvvvvvvvvvvvvvvvvvvvvv w/o being able to name it
if constexpr(std::is_same_v<std::decay_t<decltype(v)>, std::nullptr_t>) {
std::cout << "null";
} else std::cout << v;
}, literal.value);
// only difference is nullptr_t => "nullptr" vs "null"
return std::cout;
}
Also, your friend declaration doesn't match the definition. Actually, it shouldn't be friended anyway, since it needs no access to private members.
// declaration in header, outside of any class, as a free function
std::ostream &operator<<(std::ostream&, Literal const&);
// was missing const ^^^^^
I'm attempting to copy-initialize my CObj class as follows in the main() function:
#include <string>
#include <iostream>
class CObj
{
public:
CObj(std::string const& str) : m_str(str) { std::cout << "constructor" << std::endl; }
~CObj() { std::cout << "destructor" << std::endl; }
private:
std::string m_str;
};
int main()
{
CObj obj = "hello";
std::cout << "done" << std::endl;
}
However, the line CObj obj = "hello" fails to compile even though std::string is implicitly constructible from a char const*. According to my understanding here, this should work. Any reason why it doesn't? If I do this it works:
CObj obj = std::string("hello");
The literal "Hello" has type const char[6] : in order to call your constructor, two conversions are required : one to std::string and a second one to CObj .
But C++ only allows one user-defined conversion when doing an implicit conversion :
C++ Standard section § 12.3/4 [class.conv]
Type conversions of class objects can be specified by constructors and by conversion functions. These conversions are called user-defined conversions and are used for implicit type conversions
[...]
At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.
This is why this works :
CObj obj = std::string("hello");
Or this:
CObj obj("hello");
Or you could provide a constructor that accepts a const char* :
CObj(const char* cstr) : m_str(cstr) { ... }
I would always advise to make such constructors explicit to avoid unwanted implicit conversions, unless it really brings something to the user of the class.
You are limited to at most one user-defined conversion when instantiating an object (i.e. char[6] -> std::string and std::string -> CObj is one conversion too many).
To fix:
int main()
{
using namespace std::literals::string_literals; // this is necessary
// for the literal conversion
CObj obj = "hello"s; // note the extra "s", (which constructs a std::string)
std::cout << "done" << std::endl;
}
Consider the following class which contains a conversion function for the std::string type:
class SomeType
{
public:
SomeType(char *value)
{
_str = value;
}
operator std::string()
{
return std::string(_str);
}
private:
char *_str;
};
The following snippet fails to compile with the error: no operator "==" matches these operands
int main(int argc, char* argv[])
{
SomeType a("test");
if (a == std::string("test")) // ERROR on this line
{
int debug = 1;
}
return 0;
}
I realize I could define an operator== method that accepts std::string operand, but why doesn't the conversion function work?
The problem is that std::string is in fact a template and as such I imagine that it's comparison operator is also a template. And in that case the standard as I recall states that no implicit conversion will happen for the required arguments, which means you'd have to cast SomeType to string yourself for it to be called.
As stated in Item 46 of Effective C++:
[...], because implicit type conversions are never considered during template argument deduction. Never. Such conversions are used during function calls, yes, but before you can call a function, you have to know which functions exist. [...]
You can find more info here.
std::string is actually typedef to std::basic_string<char, i_do_not_remember>
There no operator == that get just std::string It's template one
template<...>
bool operator (basic_string<...>& a, basic_string<...>& b) {
//
}
But template types can't be deduced here. You may cast it manually:
if (static_cast<std::string>(a) == std::string("test"))
I am trying to write a C++ class that has some overloaded methods:
class Output
{
public:
static void Print(bool value)
{
std::cout << value ? "True" : "False";
}
static void Print(std::string value)
{
std::cout << value;
}
};
Now lets say I call the method as follows:
Output::Print("Hello World");
this is the result
True
So, why, when I have defined that the method can accept boolean and string, does it use the boolean overload when I pass in a non-boolean value?
EDIT: I come from a C#/Java environment, so quite new to C++!
"Hello World" is a string literal of type "array of 12 const char" which can be converted to a "pointer to const char" which can in turn be converted to a bool. That's precisely what is happening. The compiler prefers this to using std::string's conversion constructor.
A conversion sequence involving a conversion constructor is known as a user-defined conversion sequence. The conversion from "Hello World" to a bool is a standard conversion sequence. The standard states that a standard conversion sequence is always better than a user-defined conversion sequence (§13.3.3.2/2):
a standard conversion sequence (13.3.3.1.1) is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence
This "better conversion sequence" analysis is done for each argument of each viable function (and you only have one argument) and the better function is chosen by overload resolution.
If you want to make sure the std::string version is called, you need to give it an std::string:
Output::Print(std::string("Hello World"));
Not sure why nobody posted this, but you can add another overload that converts from const char* to std::string for you. This saves the caller from having to worry about this.
class Output
{
public:
static void Print(bool value)
{
std::cout << value ? "True" : "False";
}
static void Print(std::string value)
{
std::cout << value;
}
// Just add the override that cast to std::string
static void Print(const char* value)
{
Output::Print(std::string(value));
}
};
FWIW, it can be addressed this way (if templates can be used), if you don't want to add overloads for const char*.
#include <iostream>
#include <string>
#include <type_traits>
template <typename Bool,
typename T = std::enable_if_t<std::is_same<Bool, bool>{}>>
void foo(Bool)
{
std::cerr << "bool\n";
}
void foo(const std::string&)
{
std::cerr << "string\n";
}
int main()
{
foo("bar");
foo(false);
}
Since C++14 we have the operator""s from the std::string_literals namespace, which can be used to tell the compiler to bind to the string (or string_view in C++17) overload:
using namespace std::string_literals;
Output::Print("Hello World"s);
Prints: Hello World
This the below program i have written for some test.
class tgsetmap
{
public:
std::map<std::string,std::string> tgsetlist;
void operator<<(const char *str1,const char *str2)
{
tgsetlist.insert( std::map<std::string,std::string>::value_type(str1,str2));
}
};
int main()
{
tgsetmap obj;
obj<<("tgset10","mystring");
obj.tgsetlist.size();
}
This throws a compilation error:
"test.cc", line 10: Error: Illegal number of arguments for tgsetmap::operator<<(const char, const char*).
"test.cc", line 22: Error: The operation "tgsetmap << const char*" is illegal.
2 Error(s) detected.*
Am i wrong some where?
You can't force operator<< to take two arguments on right-hand side. The following code:
obj<<("tgset10","mystring");
does not work as a function call with two arguments but instead just uses the , operator. But it's probably not what you are interested in.
If you need to pass two arguments to the << operator, you need to wrap them in some other (single) type. For example, you could use the standard std::pair, i.e. std::pair<const char*, const char*>.
But note that the operator<< should also return some reasonable type suitable for << chaining. That would probably be a tgsetmap& in your case. The following version should work fine:
#include <map>
#include <string>
#include <iostream>
class tgsetmap
{
public:
typedef std::map<std::string, std::string> list_type;
typedef list_type::value_type item_type;
list_type tgsetlist;
tgsetmap& operator<<(item_type item)
{
tgsetlist.insert(item);
return *this;
}
};
int main()
{
tgsetmap obj;
obj << tgsetmap::item_type("tgset10","mystring")
<< tgsetmap::item_type("tgset20","anotherstring");
std::cout << obj.tgsetlist.size() << std::endl;
}
Note that I've added typedefs to not have to repeat the type names over and over again. I've also made operator<< return a tgsetmap& so that << could be chained (used like in the modified main() above). And finally, I've reused the std::map<...>::value_type to make it simpler but you could also use any other type of your own.
But I believe that you may prefer using a regular method instead. Something like:
void add(const char *str1, const char *str2)
{
tgsetlist.insert( std::map<std::string, std::string>::value_type(str1, str2));
}
(inside the class declaration), and then:
obj.add("tgset10", "mystring");
The operator<< inside of a class must be overloaded like this:
T T::operator <<(const T& b) const;
If you want to overload it with 2 arguments, you can do it outside of a class:
T operator <<(const T& a, const T& b);
My compiler, for example, gives a more detailed error message for the code you posted:
If you are not sure about an operator overloading syntax, there is a wiki article about it.
Yes. operator << is binary operator. not ternary. not forget about this pointer.
As mentioned, the << is binary operator, so there is no way it can take more than two args(One should be this if you are declaring inside the class or a LHS if you are declaring outside the class). However you can accomplish the same functionality by doing obj<<"tgset10". <<"mystring";. But since << is a binary operator, you have to do some hack for this.
For this, I ve assigned a static variable op_count, where in I will determine if it is the value or the type. And another static variable temp_str to store the previous value across invocations.
class tgsetmap
{
public:
std::map<std::string,std::string> tgsetlist;
static int op_count = 0;
static const char *temp_str;
tgsetmap& operator<<(const char *str)
{
op_count++;
if (op_count%2 != 0) {
temp_str = str;
}
else {
tgsetlist.insert( std::map<std::string,std::string>::value_type(temp_str,str));
}
return this;
}
};
So you can do
int main()
{
tgsetmap obj;
obj<<"tgset10"<<"mystring";
obj.tgsetlist.size();
}
Or simply you can embed the value and type in the same string using some separator,
value:type = separator is :
value_type = separator is _.