6

I have several types and I want to "bind" an std::integral_constant sequential ID value to every type at compile-time.

Example:

struct Type00 { };
struct Type01 { };
struct Type02 { };
struct Type03 { };
struct TypeXX { };
struct TypeYY { };

template<typename T> struct TypeInfo
{
    using Id = std::integral_constant<int, ???>;
};

int main()
{
     cout << TypeInfo<Type00>::Id::value; // Should always print 0
     cout << TypeInfo<Type01>::Id::value; // Should always print 1
     cout << TypeInfo<Type02>::Id::value; // Should always print 2
     cout << TypeInfo<Type03>::Id::value; // Should always print 3
     cout << TypeInfo<TypeXX>::Id::value; // Should always print 4
     cout << TypeInfo<TypeYY>::Id::value; // Should always print 5
}

The problem is that I don't know how to keep track of the last used ID. Ideally, I want something like:

template<typename T> struct TypeInfo
{
    using Id = std::integral_constant<int, lastUsedID + 1>;
};

Is there any way to define and keep track of a compile-time lastUsedID?

How can I solve this problem?


EDIT:

Clarifications:

  • TypeInfo<...> needs to be frequently called in user code. The syntax must remain clear (the user doesn't (need to know there's a) / (manually increment the) compile-time counter)
  • Typeinfo<T>::Id::value must always return the same value in the whole program. The initial value will be "bound" on the first instantiation, to lastUsedID + 1.
  • I can use all C++11 and C++14 features.
  • Listing all types before calling TypeInfo<...> is not a proper solution.
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • you could inherit every consecutive one on the next one and increment. Or something. – Bartek Banachewicz Sep 18 '14 at 15:40
  • 1
    How about between compilation units (different source files), need the values agree? Not overlap? Are you willing to make a list of all types somewhere? Do they have to be consecutive, or just unique? Knowing where exactly you can take less than all can make your problem much easier to solve. – Yakk - Adam Nevraumont Sep 18 '14 at 15:41
  • @BartekBanachewicz: can you elaborate? The syntax needs to be as the one in my example, as users of my library will need to call `TypeInfo<...>` themselves in their code. --- @Yakk: `TypeInfo::Id::value` must always return the same value for `T` in the whole program. – Vittorio Romeo Sep 18 '14 at 15:49
  • @PiotrS.: Unfortunately, no. – Vittorio Romeo Sep 18 '14 at 15:55
  • Maybe create an enum with all ID values and a macro like ID(Type)? – Anton Savin Sep 18 '14 at 16:08
  • 1
    What about [BOOST_PP_COUNTER](http://www.boost.org/doc/libs/1_49_0/libs/preprocessor/doc/ref/counter.html) or [this question: Does C++ support compile-time counters?](http://stackoverflow.com/questions/6166337/does-c-support-compile-time-counters?lq=1) – firda Sep 18 '14 at 16:10
  • @firda: I'm pretty sure both links aren't helpful: I can't seem to think of a way I could bind a `BOOST_PP_COUNTER` unique value to a specific type, nor how I could use any answer in the question you linked to do so. Also, I'd prefer not introducing a boost dependency in my libraries. – Vittorio Romeo Sep 18 '14 at 16:15
  • I don't think there is a solution unless you name all the types in one header, in defined order. Once this is done, you can use similar technique from either the boost counter or the question. – firda Sep 18 '14 at 16:28
  • Is creation of *typelist* as in [building-and-accessing-a-list-of-types-at-compile-time](http://stackoverflow.com/questions/18701798/building-and-accessing-a-list-of-types-at-compile-time) possible ? – Jarod42 Sep 18 '14 at 17:37
  • Can it be a runtime const IDs? I mean assigned during program startup? – PiotrNycz Sep 18 '14 at 19:12
  • @PiotrNycz: nope, that's my current solution (using `static` variables)... I need something that works at compile-time though. – Vittorio Romeo Sep 18 '14 at 19:26
  • I meant `static const` like in this example code: http://ideone.com/9P0PHR . BTW can you add in your question in which context, why do you need compile time IDs? Maybe there are other solutions. It seems that compile time IDs are rather impossible with all your constraints... – PiotrNycz Sep 18 '14 at 19:58
  • @PiotrNycz: Yeah I'm doing something very similar to that - anyway, I found myself in need of compile-time IDs in multiple occasions: 1) bitset creation for component-based entity system where every component type is bound to a specific bit ; 2) polymorphic memory manager where every derived type is stored in a chunk contained in a contiguous array of chunks where the ID is the index ; and more... – Vittorio Romeo Sep 18 '14 at 20:25
  • @VittorioRomeo: can you have at least all those types declared or included in a single collective header, so that when someone wants to use any class then he/she includes that single header inclluding others? – Piotr Skotnicki Sep 19 '14 at 14:43
  • 1
    You mey be interested in [this answer](http://stackoverflow.com/a/23231020/3043539). – Constructor Sep 19 '14 at 18:07
  • 1
    If the types may be defined in different headers then I'm pretty sure there is no way in standard C++ to have consecutive, compile-time constant type IDs which are guaranteed to hold the same values in the whole program, without providing an explicit list of types. This is because different parts of a program are compiled separately. If `a.cpp` contains `TypeInfo; TypeInfo;` and `b.cpp` contains `TypeInfo; TypeInfo;` then a counter-increment-on-read approach would yield different IDs. You need to drop _one_ of your requirements. – Oktalist Sep 20 '14 at 17:36
  • https://stackoverflow.com/a/69996234/5556374 you could use source_location. in c++17+ – I.Omar Nov 16 '21 at 21:32

2 Answers2

3

It is not possible and it's quite easy to see why: Consider two compilation units. Unit one sees type Type00, but not Type01, while unit two sees Type01 but not Type00. There is nothing in C++ (including C++11 and C++14) which could now tell the compiler in both compilation units which order those types should have. Even adding some data to the object file for link-time is too late, as you are asking for a compile-time value. This is the fundamental concept of compilation units which poses a hard barrier for the feature you are asking for.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • I understand now, thanks. I have one more question before awarding the bounty: since it isn't possible to do what I want at compile-time, is [this implementation using static storage](http://ideone.com/6yE4tP) acceptable and safe even in the case of multiple compilation units? – Vittorio Romeo Sep 21 '14 at 15:58
  • @VittorioRomeo Yes, it works, but it's a different thing. Static storage is not a compile-time value, it's a load-time initialized static (const) value. It can't be used as a real compile-time value, e.g., as a template parameter. – Daniel Frey Sep 21 '14 at 20:33
  • I am aware of that. I just wanted to make sure this "workaround" didn't have issues with multiple compilation units. – Vittorio Romeo Sep 21 '14 at 20:57
  • 1
    @VittorioRomeo As I said, it works - meaning there should be no issues with multiple compilation units (of course bugs are always possible). I just tried to emphasize where the difference is and how your technique avoids the problem of the original question by delaying the calculation of the value to a later stage. – Daniel Frey Sep 21 '14 at 22:03
1

You could do this with a type list:

template <typename... Ts>
struct TypeList;

template <>
struct TypeList<>
{
    static const int size = 0;
    static std::integral_constant<int, -1> indexOf(...);
};

template <typename Head, typename... Tail>
struct TypeList<Head, Tail...> : TypeList<Tail...>
{
    static const int size = sizeof...(Tail) + 1;
    static std::integral_constant<int, sizeof...(Tail)> indexOf(Head&&);
    using TypeList<Tail...>::indexOf;
};

template <typename TypeList, typename T>
using IndexOf = std::integral_constant<int,
    TypeList::size - decltype(TypeList::indexOf(std::declval<T>()))::value - 1>;

If T is not present in List then IndexOf<List, T>::value is -1. You can cause this case to be a compile error instead, by removing the ellipsis from the signature TypeList<>::indexOf(...).

Usage:

struct Type00 { };
struct Type01 { };
struct Type02 { };
struct Type03 { };
struct TypeXX { };
struct TypeYY { };

using MyTypeList = TypeList<
    Type00,
    Type01,
    Type02,
    Type03,
    TypeXX,
    TypeYY
>;

int main()
{
    std::cout << IndexOf<MyTypeList, Type00>::value
              << IndexOf<MyTypeList, Type01>::value
              << IndexOf<MyTypeList, Type02>::value
              << IndexOf<MyTypeList, Type03>::value
              << IndexOf<MyTypeList, TypeXX>::value
              << IndexOf<MyTypeList, TypeYY>::value;
}

Demo

Oktalist
  • 14,336
  • 3
  • 43
  • 63