Reputation: 1281
My goal is to design a String class that decorates std::string in order to provide some functionality my program needs. One functionality I want to add is the ability to convert anything to my String implicitly in order to save some typing.
In order to achieve the implicitly conversion I designed the following class:
std::ostream& operator<<(std::ostream& o, const String& s);
class String {
public:
template<typename t_value>
String::String(t_value value) {
std::ostringstream oss;
oss << value;
_str = oss.str();
}
private:
std::string _str;
}
This works fine with any type that has the <<
operator defined. The problem came up with any class that doesn't have the stream operator. A compiler error would be fine but what I got is an infinity recursion since C++ tries to use my global <<
operator to try to convert to my String type.
My primary objective is to code like this
class Foo {
int _memberWithUnderscoreInName;
}
String s = Foo();
And get a compiler error instead of an infinite loop in the constructor.
Is there a simple solution for this?
Upvotes: 4
Views: 3863
Reputation:
This or similar should fix it:
namespace HasFormattedOutput {
namespace Detail
{
struct Failure{};
}
template<typename OutputStream, typename T>
Detail::Failure operator << (OutputStream&, const T&);
template<typename OutputStream, typename T>
struct Result : std::integral_constant<
bool,
! std::is_same<
decltype((*(OutputStream*)0) << std::declval<T>()),
Detail::Failure
>::value
> {};
} // namespace HasFormattedOutput
template <typename T, typename OutputStream = std::ostream>
struct has_formatted_output
: HasFormattedOutput::Result<OutputStream, T>
{};
class X {
public:
X() {}
template <typename T>
X(const T& t) {
static_assert(
has_formatted_output<T>::value,
"Not supported type.");
std::ostringstream s;
s << t;
str = s.str();
}
private:
std::string str;
};
std::ostream& operator << (std::ostream& stream, const X&) { return stream; }
struct Y {
Y() {}
};
int main() {
Y y;
X x(y);
return 0;
}
Upvotes: 2
Reputation: 254471
Instead of declaring the output operator in the surrounding namespace, only declare it as a friend of the String
class:
class String {
public:
// This must be implemented inline here
friend std::ostream& operator<<(std::ostream& o, const String& s) {
return o << _str; // for example
}
template<typename t_value>
String(t_value value) {
std::ostringstream oss;
oss << value;
_str = oss.str();
}
private:
std::string _str;
};
Now it can only be found by argument-dependent lookup, and so will only be considered if the second argument really is of type String
, not just convertible to it. Therefore, it won't be considered as a candidate for os << value
in the constructor, giving a compile error rather than a run-time death spiral if there is no other candidate.
Upvotes: 5