I wish to create an alternative to std::type_index
that does not require RTTI:
template <typename T>
int* type_id() {
static int x;
return &x;
}
Note that the address of the local variable x
is used as the type ID, not the value of x
itself. Also, I don't intend to use a bare pointer in reality. I've just stripped out everything not relevant to my question. See my actual type_index
implementation here.
Is this approach sound, and if so, why? If not, why not? I feel like I am on shaky ground here, so I am interested in the precise reasons why my approach will or will not work.
A typical use case might be to register routines at run-time to handle objects of different types through a single interface:
class processor {
public:
template <typename T, typename Handler>
void register_handler(Handler handler) {
handlers[type_id<T>()] = [handler](void const* v) {
handler(*static_cast<T const*>(v));
};
}
template <typename T>
void process(T const& t) {
auto it = handlers.find(type_id<T>());
if (it != handlers.end()) {
it->second(&t);
} else {
throw std::runtime_error("handler not registered");
}
}
private:
std::map<int*, std::function<void (void const*)>> handlers;
};
This class might be used like so:
processor p;
p.register_handler<int>([](int const& i) {
std::cout << "int: " << i << "\n";
});
p.register_handler<float>([](float const& f) {
std::cout << "float: " << f << "\n";
});
try {
p.process(42);
p.process(3.14f);
p.process(true);
} catch (std::runtime_error& ex) {
std::cout << "error: " << ex.what() << "\n";
}
Conclusion
Thanks to everyone for your help. I have accepted the answer from @StoryTeller as he has outlined why the solution should be valid according the rules of C++. However, @SergeBallesta and a number of others in the comments have pointed out that MSVC performs optimizations which come uncomfortably close to breaking this approach. If a more robust approach is needed, then a solution using std::atomic
may be preferable, as suggested by @galinette:
std::atomic_size_t type_id_counter = 0;
template <typename T>
std::size_t type_id() {
static std::size_t const x = type_id_counter++;
return x;
}
If anyone has further thoughts or information, I am still eager to hear it!
handlers
map is mainly used as an indirection tool for the execution, it is expected to be much faster if replaced by a vector, and using some auto-incremented enum for the types instead of some random pointers. – Simonsunordered_map
instead of amap
, the performance may vary a lot and you don't use any ordering – Coppervector
if theprocessor
wasn't the only thing usingtype_id
. Anunordered_map
or a sortedvector
would probably be faster than amap
. In reality, the "handlers" would be doing far more work than they are in the example, so the lookup time for the routines would probably be negligible. – Timmid
is misleading, since the address is the actual ID, but I thought the question would be clear from the title. Of course I would comment this code in real life. And I chose not to cast touintptr_t
orvoid*
because I wanted to keep things as simple as possible. I'd rather not risk invoking additional C++ voodoo if it isn't necessary. – Timmvoid *
. – Barelavoid*
(isocpp.org/wiki/faq/…). – Timmreinterpret_cast
s of the same pointer). – Timm/opt:icf
linker option. – Helotism