我的目标是设计一个String类来装饰std :: string,以便提供我的程序所需的一些功能。我想要添加的一个功能是能够隐式地将任何东西转换为我的String,以节省一些输入。
为了实现隐式转换,我设计了以下类:
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;
}
这适用于任何定义了<<
运算符的类型。任何没有流操作符的类都会出现问题。编译器错误没问题,但我得到的是无限递归,因为C ++试图使用我的全局<<
运算符来尝试转换为我的String类型。
我的主要目标是像这样编码
class Foo {
int _memberWithUnderscoreInName;
}
String s = Foo();
在构造函数中得到编译器错误而不是无限循环。
这有一个简单的解决方案吗?
答案 0 :(得分:5)
不是在周围的命名空间中声明输出运算符,而是仅将其声明为String
类的朋友:
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;
};
现在它只能通过参数依赖查找找到,因此只有在第二个参数确实是String
类型时才会被考虑,而不仅仅是可转换为它。因此,它不会被视为构造函数中os << value
的候选者,如果没有其他候选者,则会产生编译错误而不是运行时死亡螺旋。
答案 1 :(得分:2)
这个或类似的应该解决它:
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;
}