Like my posting about cloning this posting about unique identifier values is in preparation for discussing the cppx library’s exception translation support. In short the aspect discussed here is how to let the calling code choose an id for a set of installed translators, so that it can remove them all in one operation (specifying that id). And the simplest id you have in C++ is a type with external linkage. You can obtain a unique-wrt.-comparisions id for a class from the typeid
operator. The only problem is that that id, of type std::type_info
, is not copyable and not generally comparable, so it can’t be used directly as a key in a std::map
, say, and the standard fails to guarantee that you will always obtain the same std::type_info
instance for the same type, so it’s a bit risky to use a pointer to that instance…
Andrei’s solution: a wrapper class for std::type_info
If there is some general problem that primarily involves C++ types, then Andrei Alexandrescu will probably have solved it already, and such a solution will probably also be present in the Boost library. And yes, a copyable, generally comparable wrapper for std::type_info
was one of the first things that Andrei discussed in his “Modern C++ Design” book (most of the discussion in that book concerned the techniques employed in Andrei’s Loki library). If you don’t have that book, get it! 🙂
Andrei called his class TypeInfo
, and that’s also what I call the cppx library’s version – it is of course in a different namespace…
In short, the only purpose of this class is to avoid a library dependency, where if this class hadn’t been defined by cppx itself then cppx would have needed to rely on the Loki library, and/or the Boost library, or some such.
The cppx TypeInfo
class
File [progrock/cppx/generic/TypeInfo.h]:
// Copyright (c) Alf P. Steinbach, 2010. // // A copyable and comparable type info class, inspired by Andrei's almost identical // class in the Loki library. I just left out the "compatibility" functions 'before'. // Essentially this provides internal-to-the-process unique identifier values. // // #include <progrock/cppx/generic/typeinfo.h> #ifndef PROGROCK_CPPX_GENERIC_TYPEINFO_H #define PROGROCK_CPPX_GENERIC_TYPEINFO_H #include <progrock/cppx/devsupport/better_experience.h> //------------------------------------- Dependencies: #include <typeinfo> //------------------------------------- Interface: namespace progrock{ namespace cppx{ class TypeInfo { private: std::type_info const* pStdInfo_; public: virtual ~TypeInfo() {} TypeInfo( std::type_info const& stdInfo ) : pStdInfo_( &stdInfo ) {} char const* name() const { return pStdInfo_->name(); } bool operator!=( TypeInfo const& other ) const { return !!(*pStdInfo_ != *other.pStdInfo_); // "!!" for MSVC non-std ops. } bool operator<( TypeInfo const& other ) const { return !!pStdInfo_->before( *other.pStdInfo_ ); // "!!" for MSVC non-std ops. } bool operator<=( TypeInfo const& other ) const { return !other.pStdInfo_->before( *pStdInfo_ ); } bool operator==( TypeInfo const& other ) const { return !!(*pStdInfo_ == *other.pStdInfo_); // "!!" for MSVC non-std ops. } bool operator>=( TypeInfo const& other ) const { return !pStdInfo_->before( *other.pStdInfo_ ); } bool operator>( TypeInfo const& other ) const { return !!other.pStdInfo_->before( *pStdInfo_ ); // "!!" for MSVC non-std ops. } }; }} // namespace progrock::cppx #endif
How this class differs from Andrei’s
I just typed in and tested the above, and then I looked in “Modern C++ Design” to see what Andrei did differently, if anything. Well there was not much. For some reason he put the comparison operators outside the class, as free-standing functions, and he included a before
member function like the one in std::type_info
.
Those differences are probably just about style.
And I guess, but I don’t know, that Andrei did not add those “!!
” bang-bangs. I added them because MSVC 7.1 has non-standard int
result type for the bool
functions in std::type_info
. And due to its own lack of standard-conformance wrt. to the result types, without the bang-bangs it issues a stream of what in this context are pure sillywarnings about allegedly inefficient conversion from int
to bool
!
How unique is a TypeInfo
instance as an id?
With respect to comparison to other TypeInfo
instances, a TypeInfo
instance is almost as unique as a class.
Hey, “almost”? What do you mean? Why the weasel word?
Well, the C++ standard does not address the issue of dynamic libraries. And in Windows a dynamic library, a Windows DLL, is almost like a program on its own, unlike a *nix dynamic library which is more like a part of the client program. So if you pass a TypeInfo
instance to a Windows DLL then there is no formal guarantee, and not even an in-practice guarantee, that the DLL won’t be able to produce a TypeInfo
instance that compares as equal. In Windows terminology the uniqueness (with respect to comparisons) is only within a module. But happily that’s good enough for most C++ programming.
Usage considerations
With a type as argument the typeid
operator requires that type to be complete (a complete type is one that you can give to sizeof
). And so to avoid imposing needless verbosity on client code, when a TypeInfo
instance is used as an id it may be a good idea to form a pointer type. For even when T is incomplete, T*
is complete.
AFAIK, MSVC compares the type_infos by its internal string representation, so they are comparable even across DLL borders, but I don’t know if it really works or if there are some more pitfalls.
Besides, this would have been a good class to introduce/discuss the Barton-Nackman-trick. 😉
Thomas aka Phygon
PS: Nice blog!
Hm. I would not recommend comparing
type_info
s across DLL borders even if MSVC supports that (but your description sounds as iftype_info
s for classFoo
in module 1 andFoo
in module 2 might compare equal with MSVC). It’s like all undefined behavior: it’s undefined.Re the Barton-Nackman trick, thanks! I only remembered that it had something to do with the Curiously Recurring Template Pattern, and had to look it up in Wikipedia. It wouldn’t have saved anything here, though, but it’s interesting on its own. One day I’m going to buy that book, seriously!
The Barton-Nackman technique that I do remember (because it’s more commonly useful) is the Barton-Nackman
Fallible
class, which I think is calledOptional
in Boost. It enables calling code to do simple checking rather than dealing with an exception when a result-producing function fails to produce a result. But throwing an exception, or aborting, if an attempt is made to actually use a non-existent result.Cheers,
– Alf
Making the comparison operators global rather than class members makes them more usable as conversions work as expected for both left and right argument rather than only for the right argument.
First note that since
TypeInfo( std::type_info const& stdInfo )
is not explicitstd::type_info
object can be implicitly converted toTypeInfo
. So with global operators it would be possible to writetypeof( MyClass ) == myClassTypeInfo
where
myClassTypeInfo
is ofTypeInfo
. While the same is not possible with operators being members. Making the comparison the other way works in both cases.I think in general comparison operators should always be global (same as general arithmetic operators).
Thanks! Why didn’t I think of that? 🙂