Absence of Typeof Operator in C++03

Absence of typeof operator in C++03?

It just uses compiler magics. Like, GCC's __typeof__. For compilers that don't provide such magic, it provides an emulation that can detect the type of some expressions, but fails with completely unknown types.

A possible implementation could be to have a list of functions that accept an expression of a given type, and then dispatch from that type to a number using a class template. To make the function template return the number as a compile time entity, we put it into an array dimension

template<typename> struct type2num;
template<int> struct num2type;
template<typename T> typename type2num<T>::dim &dispatch(T const&);

Then it goes from that number back to the type, so that our EMUL_TYPEOF could directly name the type. So to register a type, we write

#define REGISTER_TYPE(T, N) \
template<> \
struct type2num<T> { \
static int const value = N; \
typedef char dim[N]; \
}; \
template<> \
struct num2type<N> { typedef T type; }

Having this in place, you can write

#define EMUL_TYPEOF(E) \
num2type<sizeof dispatch(E)>::type

Whenever you need to register a type, you write

REGISTER_TYPE(int, 1);
REGISTER_TYPE(unsigned int, 2);
// ...

Of course, now you find you need a mechanism to accept vector<T>, where you don't know T in advance and then it gets arbitrary complex. You could create a system where the numbers mean more than just a type. This could probably work:

#define EMUL_TYPEOF(E) \
build_type<sizeof dispatch_1(E), sizeof dispatch_2(E)>::type

This could detect types like int and also types like shared_ptr<int> - in other words, types that aren't class template specializations, and class template specializations with one template argument, by doing some kind of systematical mapping

  • If the first number yields 1, the second number specifies a type; otherwise
  • the first number specifies a template, and the second number its first type template argument

So this becomes

template<int N, int M>
struct build_type {
typedef typename num2tmp<N>::template apply<
typename num2type<M>::type>::type type;
};

template<int N>
struct build_type<1, N> {
typedef num2type<N>::type type;
};

We also need to change the dispatch template and split it up in two versions, shown below, alongside the REGISTER_TEMP1 for registering one-argument templates

template<typename T> typename type2num<T>::dim1 &dispatch_1(T const&);
template<typename T> typename type2num<T>::dim2 &dispatch_2(T const&);

#define REGISTER_TYPE(T, N) \
template<> \
struct type2num<T> { \
static int const value_dim1 = 1; \
static int const value_dim2 = N; \
typedef char dim1[value_dim1]; \
typedef char dim2[value_dim2]; \
}; \
template<> \
struct num2type<N> { typedef T type; }

#define REGISTER_TMP1(Te, N) \
template<typename T1> \
struct type2num< Te<T1> > { \
static int const value_dim1 = N; \
static int const value_dim2 = type2num<T1>::value_dim2; \
typedef char dim1[value_dim1]; \
typedef char dim2[value_dim2]; \
}; \
template<> struct num2tmp<N> { \
template<typename T1> struct apply { \
typedef Te<T1> type; \
}; \
}

Registering the std::vector template and both int variants now look like

REGISTER_TMP1(std::vector, 2);
// ... REGISTER_TMP1(std::list, 3);

REGISTER_TYPE(int, 1);
REGISTER_TYPE(unsigned int, 2);
// ... REGISTER_TYPE(char, 3);

You probably also want to register multiple numbers with each type, one number for each combination of const/volatile or may need more than one number per type for recording *, & and such. You also want to support vector< vector<int> >, so you need more than one number for the template argument too, making build_type call itself recursively. As you can create an arbitrary long list of integers, you can encode anything into that sequence anyway, so it's just up to your creativity on how to represent these things.

In the end, you are probably reimplementing BOOST_TYPEOF :)

A Portable typeof() in C++ using SFINAE?

It's impossible in the general case. And yes, quite a lot of effort has been put into this problem over the years.

There are a lot of tricks for determining the type of an expression in specific cases (for example, functors are commonly annotated with a typedef indicating their return type), but in general, you need decltype, which is precisely why it was added in C++0x. The standards committee follows a policy of not adding core language features to do something a library could have done just as well.

Access-specifiers are not foolproof?

But we can make it accessible without editing the class! All we need to do is this,

Technically, all you've shown is that "we can turn a legal program into Undefined Behavior" without editing one specific class.

That's hardly news. You can also turn it into undefined behavior just by adding a line such as this to the end of main():

int i = 0;
i = ++i;

Access specifiers in C++ are not a security feature They do not safeguard against hacking attempts, and they do not safeguard against people willfully trying to introduce bugs into you code.

They simply allow the compiler to help you maintain certain class invariants. They allow the compiler to inform you if you accidentally try to access a private member as if it were public. All you've shown is that "if I specifically try to break my program, I can". That, hopefully, should be a surprise to absolutely no one.

As @Gman said, redefining keywords in the C++ language is undefined behavior. It may seem to work on your compiler, but it is no longer a well-defined C++ program, and the compiler could in principle do anything it likes.

JavaScript types

About typeof null == 'object', this is a mistake that comes since the early days, and unfortunately this mistake will stay with us for a long time, it was too late to be fixed in the ECMAScript 5th Edition Specification.

About the functions, they are just objects, but they have an special internal property named [[Call]] which is used internally when a function is invoked.

The typeof operator distinguish between plain objects and functions just by checking if the object has this internal property.

Type-traits experimentation, SFINAE seems not to be working, whines about the absence of something in the form of an error

The problem is here:

virtual
typename EnableIf<T::hasEquality::value, int>::type
count(const T& what) const =0;

You hit another example where generic programming (templates) and object oriented programming styles conflict.

SFINAE is a metaprogramming technique that works with templates. Despite the appearence (the use of T), the function declared above is not a template. It's a normal function inside a template class. The template type parameter T is a parameter of List and not of count.

For instance, the following is an example of SFINAE:

template<class T>
class List {
public:
template<class T>
class List {
public:
// ...
template <typename U>
typename EnableIf<std::is_same<U, T>::value && U::hasEquality::value, int>::type
count(const U& what) const { std::cout << "1\n"; }

template <typename U>
typename EnableIf<std::is_same<U, T>::value && !U::hasEquality::value, int>::type
count(const U& what) const { std::cout << "2\n"; }
};

};

int main() {
A a(1);
B b;
List<A> la;
la.count(a); // outputs 1
List<B> lb;
lb.count(b); // ouputs 2
}

Notice that the two counts are now a templates (parametrized on U). Both are active only if T is the same type as U. This is a workaround to accept T only (it's not perfect, for instance, it discards implicit conversions). The first requires, in addition, that U::hasEquality::value == true and the second requires the opposite.

The key point here is that SFINAE works on templates.

But as you can see I changed your design and made count non virtual. Unfortunately, you cannot make the count functions above virtual because template functions cannot be virtual.

The basic issue is as follows. Template functions are instanciated only when they are called. So when the compiler parses List (my version) it doesn't know yet all the instantiations of count that are going to exist.

For each virtual function there should be an entry in the virtual table and when the compiler parses List it must know how many entries there are in the virtual table.

Hence, on one hand, when parsing List the compiler doesn't know the number of template instancitaions and, on the other hand, it must know the number of virtual functions. The conclusion is that template functions cannot be virtual.

C++ macro '##' doesn't work after '-' operator

The preprocessor works on "tokens" - likes names and operators.

The ## operator creates a new token by pasting smaller parts together. In the first example set_##type##_value becomes set_a_value, which is a valid token.

In the second example ->##type##_value would become ->a_value, which is not a valid preprocessor token. It ought to be two tokens.

If you just make the line x->type##_value(); it should work. You get the separate tokens x, ->, a_value, (, ), and ;.

Getting the type of a member

template <class T, class M> M get_member_type(M T:: *);

#define GET_TYPE_OF(mem) decltype(get_member_type(mem))

Is the C++11 way. It requires you to use &Person::age instead of Person::age, although you could easily adjust the macro to make the ampersand implicit.

How do I typecast with type_info?

I don't think such casting can be done. Suppose you could do "dynamic" casting like this at runtime (not to mean dynamic_cast). Then if you used the result of the cast to call a function the compiler could no longer do type checking on the parameters and you could invoke a function call that doesn't actually exist.

Therefore it's not possible for this to work.



Related Topics



Leave a reply



Submit