We are working with an inhouse library which features a StringBuilder
class w开发者_如何学JAVAhich is used to turn a list of VariableValue
objects into a string. VariableValue
objects can be constructed from arbitrary types (by specializing a convertVariable
template function). Here's the code which describes the scenario:
struct VariableValue {
// Construct a 'VariableValue' object, a variant type which can represent values of
// one of four types: string, number (integer), boolean and floating point.
explicit VariableValue( const std::string &serializedData );
// Getters, typesafe; will yield an exception when calling the wrong getter.
const std::string &asString() const;
bool asBoolean() const;
// ..
// Convert any VariableValue object into a string
static std::string convertToString( const VariableValue &v );
};
// Template to be specialized so that user types can be casted into a
// VariableValue object
template <typename T>
VariableValue convertVariable( T v );
// Helper class to 'concatenate' multiple VariableValue objects into a single string.
class StringBuilder {
public:
const std::string &result() const;
template <class T>
StringBuilder &operator<<( T v ) {
return *this << convertVariable( v );
}
private:
std::ostringstream m_stream;
};
template <>
inline StringBuilder &StringBuilder::operator<<( const VariableValue &v ) {
m_stream << VariableValue::convertToString( v );
return *this;
}
This all woreds very well. Clients just had to provide an appropriate specialization for the convertVariable
template (our library already provides plenty of specializations for various types) and then StringBuilder can be used. Almost.
The problem with this is that it doesn't work with types which are not copyable. All template functions take their argument by value. And in the case of the convertVariable
template it's quite expensive to change the signature (because there are quite a lot of specializations). So even though I can make the StringBuilder::operator<<
template take a const T &
, this won't help much since the convertVariable
instantiation will be just called with a T
(since the reference-to-const part is stripped while deducing the template types). If I fix this by specifying the type explicitely, as in:
class StringBuilder {
public:
// ...
template <class T>
StringBuilder &operator<<( const T &v ) {
return *this << convertVariable<const T &>( v );
}
};
The linker will complain because it no longer finds the old specializations (like e.g. template <> VariableValue convertVariable( int )
) since it looks for specializations which take a reference-to-const.
Does anybody know how I can adjust the StringBuilder
class so that I can pass non-copyable objects (that is, objects whose type neither allows copy construction nor copy assignment) to the operator<<
function?
I'm not quite sure my answer will be of any help, but it's worth trying. From your post, I tend to think that the appropriate solution is to change the signature of convertVariable
. You say that this is expensive because there are a lot of specialization, but I think it could actually be free depending on the way you chose to 'specialize'.
This article offers a nice guideline for these kind of things :
Moral #1: If you want to customize a function base template and want that customization to participate in overload resolution (or, to always be used in the case of exact match), make it a plain old function, not a specialization. And, if you do provide overloads, avoid also providing specializations.
[...]
For another thing, function template specializations don't overload. This means that any specializations you write will not affect which template gets used, which runs counter to what most people would intuitively expect. After all, if you had written a nontemplate function with the identical signature instead of a function template specialization, the nontemplate function would always be selected because it's always considered to be a better match than a template.
Indeed, instead of specializing for a type UncopyableClass
, you could very well use overloading :
VariableValue convertVariable( const UncopyableClass &t ) { /* ... */ }
It's not a specialization but an overload, and it should work exactly as expected. Note however that StringBuilder::operator<<
must take a const reference parameter.
I don't see any advantage into using this class versus simply using the std::ostream
interface.
The soundest advise I have would be the dump the class and its bugs (std::string const& str() const
for example) and simply go ahead with the stream class by overloading operator<<
appropriately for those classes that need streaming.
精彩评论