// Statically store the last used global unique id.
inline int getLastId() noexcept
{
static int lastId{0};
return lastId++;
}
// Statically store an unique id for a specific type `T`.
template<typename T> int getIdForType() noexcept
{
static int id{getLastId()};
return id;
}
struct TypeA { };
struct TypeB { };
struct TypeC { };
int main()
{
// Whenever `getIdForType<T>()` gets instantiated,
// an unique id is "bound" to `T` for the rest of the program.
std::cout << getIdForType<TypeA>() << std::endl; // Prints '0'
std::cout << getIdForType<TypeB>() << std::endl; // Prints '1'
std::cout << getIdForType<TypeC>() << std::endl; // Prints '2'
return 0;
}// We need to "store" a counter during compile-time.
// A new keyword/contextual keyword combination is probably required for this.
// We basically "allocate" memory for this counter during compilation and
// use the counter during compilation. The counter should not exist anymore
// at run-time (or be accessible at run-time).
static template std::integral_counter<int, 0> lastIdCounter;
// ____________ _____________________ ___ _ _____________
// | | | | L name
// L contextual keyword combination | |
// | | L counter starting value
// L type L counter value type
// Everything is done at compile-time. Constexpr should work, but also using
// `std::integral_constant` with a template struct.
template<typename T> constexpr int getIdForType() noexcept
{
constexpr int id{lastIdCounter::value}; // Get the compile-time counter's current value
lastIdCounter::increment(); // Increment the compile-time counter - it will remember its last value
return id;
}
int main()
{
// Whenever `getIdForType<T>()` gets called with a certain `T`, a value from
// the compile-time counter is "bound" to that `T` for the rest of the program.
// Since it is `constexpr` and evaluated at compile-time, there's no need to store
// the id anywhere in memory - calling the `getIdForType<T>()` function can
// basically be thought of as replacing `getIdForType<T>()` with a constant
// at compile time.
std::cout << getIdForType<TypeA>() << std::endl; // Prints '0'
std::cout << getIdForType<TypeB>() << std::endl; // Prints '1'
std::cout << getIdForType<TypeC>() << std::endl; // Prints '2'
return 0;
}
Q: What happens if used in different TUs?// in file a.cpp
std::cout << getIdForType<TypeA>() << std::endl; // Prints '0'std::cout << getIdForType<TypeB>() << std::endl; // Prints '1'std::cout << getIdForType<TypeC>() << std::endl; // Prints '2'
// in file b.cppstd::cout << getIdForType<TypeC>() << std::endl; // Prints '0'
std::cout << getIdForType<TypeB>() << std::endl; // Prints '1'
std::cout << getIdForType<TypeA>() << std::endl; // Prints '2'Is that the expected result?
What about using typeid / std::typeinfo hash_code?
On Friday, 19 September 2014 16:07:25 UTC+2, TONGARI J wrote:Q: What happens if used in different TUs?// in file a.cppstd::cout << getIdForType<TypeA>() << std::endl; // Prints '0'std::cout << getIdForType<TypeB>() << std::endl; // Prints '1'std::cout << getIdForType<TypeC>() << std::endl; // Prints '2'// in file b.cppstd::cout << getIdForType<TypeC>() << std::endl; // Prints '0'std::cout << getIdForType<TypeB>() << std::endl; // Prints '1'std::cout << getIdForType<TypeA>() << std::endl; // Prints '2'Is that the expected result?No - the expected result is 0, 1, 2, 3, 4, 5.
typedef any_variadic_template<A, B, C, D> typeColection;
template<typename T>
constexpr int f() { return type_pos_in_param<T, typeColection>::value; }
IntroductionThere have been many occasions in my games/libraries where I need to assign a `0..n` compile-time integer to a series of types. Here are two examples:
Why don't move this types to one typedef?
typedef any_variadic_template<A, B, C, D> typeColection;
template<typename T>
constexpr int f() { return type_pos_in_param<T, typeColection>::value; }
You can query this type anywhere you want.
I see some discussion of the ODR in this thread. Any counter usage with linkage does need to be placed in the header file and repeated exactly the same in each TU. Avoid sharing a counter between headers unless those headers are all-or-nothing.
What about using typeid / std::typeinfo hash_code?
On Friday, 19 September 2014 19:23:22 UTC+2, David Krauss wrote:I see some discussion of the ODR in this thread. Any counter usage with linkage does need to be placed in the header file and repeated exactly the same in each TU. Avoid sharing a counter between headers unless those headers are all-or-nothing.Can you elaborate more on this? What I am currently doing is putting the workaround code in an header file (header-only library) and using inline in every function. Is that not guaranteed to return expected values in different TUs? (values that never repeat, I mean)
That said... I don't think Vittorio's feature request is possible. There's no way to give stable compile-time IDs across translation units in the usage pattern he wants them in, even with a new magic language construct.
But you know what you could use for an identifier that is unique? The address
of the function itself.
I don't think so. I believe that the linker is not supposed to do that
for any distinct definition, be it a function or global or static;
addresses must be unique for each definition. At least one popular
implementation does do it by default but I'm fairly sure that's not
conforming behavior (and it can be turned off).
It's called Identical COMDAT Folding in their implementation. See
http://msdn.microsoft.com/en-us/library/bxwfs976.aspx for information
on controlling this behavior.