C++: type_info to distinguish types
Asked Answered
E

6

19

I know that compilers have much freedom in implementing std::type_info functions' behavior.

I'm thinking about using it to compare object types, so I'd like to be sure that:

  1. std::type_info::name must return two different strings for two different types.

  2. std::type_info::before must say that Type1 is before Type2 exclusive-or Type2 is before Type1.

    // like this:
    typeid(T1).before( typeid(T2) ) != typeid(T2).before( typeid(T1) )
    
  3. Two different specialization of the same template class are considered different types.

  4. Two different typedef-initions of the same type are the same type.

And finally:

  • Since std::type_info is not copyable, how could I store type_infos somewhere (eg: in a std::map)? The only way it to have a std::type_info always allocated somewhere (eg: on the stack or on a static/global variable) and use a pointer to it?

  • How fast are operator==, operator!= and before on most common compilers? I guess they should only compare a value. And how fast is typeid?

  • I've got a class A with a virtual bool operator==( const A& ) const. Since A has got many subclasses (some of which are unknown at compile time), I'd overload that virtual operator in any subclass B this way:

    virtual bool operator==( const A &other ) const {
      if( typeid(*this) != typeid(other) ) return false;
      // bool B::operator==( const B &other ) const // is defined for any class B
      return operator==( static_cast<B&>( other ) );
    }
    

    Is this an acceptable (and standard) way to implement such operator?

Errand answered 16/11, 2010 at 12:39 Comment(1)
Note on part 2: definitely not if typeid(Type1) == typeid(Type2), i.e. they're not distinct types ;-)Tekla
T
9

After a quick look at the documentation, I would say that :

  1. std::type_info::name always returns two different strings for two different types, otherwise it means that the compiler lost itself while resolving types and you shouldn't use it anymore.

  2. Reference tells : "before returns true if the type precedes the type of rhs in the collation order. The collation order is just an internal order kept by a particular implementation and is not necessarily related to inheritance relations or declaring order." You therefore have the guarantee that no types has the same rank in the collation order.

  3. Each instantiation of a template class is a different type. Specialization make no exceptions.

  4. I don't really understand what you mean. If you mean something like having typedef foo bar; in two separate compilation units and that bar is the same in both, it works that way. If you mean typedef foo bar; typedef int bar;, it doesn't work (except if foo is int).

About your other questions :

  • You should store references to std::type_info, of wrap it somehow.
  • Absolutely no idea about performance, I assume that comparison operators have constant time despite of the type complexity. Before must have linear complexity depending on the number of different types used in your code.
  • This is really strange imho. You should overload your operator== instead of make it virtual and override it.
Tusker answered 16/11, 2010 at 13:4 Comment(4)
About 4., I was meaning: typedef a b; typedef a c;, typeid(b)==typeid(c)? But yeah, I was sure even before posting the answer was yes. -- About before: I guess it could be constant time; it could be implemented like this: return &typeid(a) < &typeid(b);. -- About the last point: oops! I was of course meaning overload, not override.Errand
About 4, of course if you type <code>typedef a b; typedef a c;</code>, a and c are of the same type. About before, it depends if std::type_info is instantiated at compile time or runtime.Tusker
There is a conforming implementation of before that returns in constant time: take the addresses of the type_info objects, and compare them as integers. This may give differing results on different runs of the same program, but that would still be conforming.Obligato
A conforming implementation may return the same name for different types.Unimposing
P
4

Standard 18.5.1 (Class type_info) :

The class type_info describes type information generated by the implementation. Objects of this class effectively store a pointer to a name for the type, and an encoded value suitable for comparing two types for equality or collating order. The names, encoding rule, and collating sequence for types are all unspecified and may differ between programs.

From my understanding :

  1. You don't have this guarantee regarding std:type_info::name. The standard only states that name returns an implementation-defined NTBS, and I believe a conforming implementation could very well return the same string for every type.
  2. I don't know, and the standard isn't clear on this point, so I wouldn't rely on such behavior.
  3. That one should be a definite 'Yes' for me
  4. That one should be a definite 'Yes' for me

Regarding the second set of questions :

  • No, you cannot store a type_info. Andrei Alexandrescu proposes a TypeInfo wrapper in its Modern C++ Design book. Note that the objects returned by typeid have static storage so you can safely store pointers without worrying about object lifetime
  • I believe you can assume that type_info comparison are extremely efficient (there really isn't much to compare).
Pleasance answered 16/11, 2010 at 12:48 Comment(1)
For (1), the string "" is formally sufficient. It's null terminated, and the type is const char[1].Terramycin
M
3

You can store it like this.

class my_type_info
{
public:
     my_type_info(const std::type_info& info) : info_(&info){}
     std::type_info get() const { return *info_;}
private:
     const std::type_info* info_;
};

EDIT:

C++ standard 5.2.8.

The result of a typeid expression is an lvalue of static type const std::type_info...

Which means you can use it like this.

my_type_info(typeid(my_type));

The typeid function returns an lvalue (it is not temporary) and therefore the address of the returned type_info is always valid.

Myxomycete answered 16/11, 2010 at 12:51 Comment(10)
Yes, but where do I need to keep the value that my_type_info::info_ points to? Doesn't it need to be always allocated (eg on the stack or on static/global variables)?Errand
Ok, if it's static it's OK to store a pointer or a reference to a type_info returned by typeid even if such type_info gets removed from stack, thanks.Errand
It cannot be removed from stack, since it never is on the stack.Myxomycete
The guarantee you're using is "The lifetime of the object referred to by the lvalue extends to the end of the program". The fact that the lvalue expression is of "static type" const std::type_info doesn't tell you anything about the lifetime of the object, and in particular doesn't mean that the object is static. "Static type" is defined in 1.3.11.Tekla
The standard says it's a "lvalue of static type".Myxomycete
@ronag. No it doesn't. It says it's an "lvalue of static type const std::type_info". You've removed half of a noun phrase, and pretty much invented your own definition of "static type" which differs from the definition in 1.3.11. An object's lifetime is almost completely unrelated to its type, or to the static type of an expression involving that object.Tekla
@ronag, you're right, since typeid returns a const reference (I was thinking it returned a const type_info, not a type_info const &. You must be right then :)Errand
@Steve Jessop. You might be right. I don't quite understand. Will have to think about it. Either way, thank you for pointing it out.Myxomycete
If it helps understand, the first part of that sentence could be rephrased: "The result of a typeid expression is an lvalue. The static type of the lvalue expression is const std::type_info. The most-derived type of the object to which the lvalue refers is const std::type_info or const name ... "Tekla
Thank you. That made it understandable for me.Myxomycete
T
2

The current answers for questions 1 and 2 are perfectly correct, and they're essentially just details for the type_info class - no point in repeating those answers.

For questions 3 and 4, it's important to understand what precisely is a type in C++, and how they relate to names. For starters, there are a whole bunch of predefined types, and those have names: int, float, double. Next, there are some constructed types that do not have names of their own: const int, int*, const int*, int* const. There are function types int (int) and function pointer types int (*)(int).

It's sometimes useful to give a name to an unnamed type, which is possible using typedef. For instance, typedef int* pint or typedef int (*pf)(int);. This introduces a name, not a new type.

Next are user-defined types: structs, classes, unions. There's a good convention to give them names, but it's not mandatory. Don't add such a name with typedef, you can do so directly: struct Foo { }; instead of typedef struct {} Foo;. It's common to have class definitions in headers, which end up\ in multiple translation units. That does mean the class is defined more than once. This is still the same type, and therefore you aren't allowed to play tricks with macros to change the class member definitions.

A template class is not a type, it's a recipe for types. Two instantiations of a single class template are distinct types if the template arguments are different types (or values). This works recursively: Given template <typename T> struct Foo{};, Foo<Foo<int> > is the same type as Foo<Foo<Bar> > if and only if Bar is another name for the type int.

Terramycin answered 16/11, 2010 at 15:2 Comment(0)
B
1

Type_info is implementation defined so I really wouldn't rely on it. However, based on my experiences using g++ and MSVC, assumptions 1,3 and 4 hold... not really sure about #2.

Is there any reason you can't use another method like this?

template<typename T, typename U>
struct is_same       { static bool const result = false; };

template<typename T>
struct is_same<T, T> { static bool const result = true;  };

template<typename S, typename T>
bool IsSame(const S& s, const T& t) {   return is_same<S,T>::result; }
Balneal answered 16/11, 2010 at 13:42 Comment(1)
Oh right, I should have mentioned it doesn't work for polymorphism. That's a pretty good reason for not using it:DBalneal
D
1

Since std::type_info is not copyable, how could I store type_infos somewhere (eg: in a std::map)? The only way it to have a std::type_info always allocated somewhere (eg: on the stack or on a static/global variable) and use a pointer to it?

This is why std::type_index exists -- it's a wrapper around a type_info & that is copyable and compares (and hashes) by using the underlying type_info operations

Danieldaniela answered 13/10, 2021 at 17:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.