C++0x features in VC2010 - decltype
(n3090.pdf is the current working draft of C++0x standard, it is available at https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3090.pdf)
What we have before C++0x
When do generic programming in C++, you often have to generate type from template parameters. Type traits are mainly used for this purpose, but sometimes it is still difficult or even impossible.
For example, what is the result type of the addition of two objects?
Here is an incomplete list (The complete rule can be found in n3090.pdf, 5/10):
short + char -> int
int + unsigned int -> unsigned int
unsigned int + long long -> long long
Then what about arithmetic types? User defined types?
To simplify this, GCC has an extension keyword "__typeof__" and boost provides library implementation "BOOST_TYPEOF" to deduce the type from the expression.
What we have now in C++0x
decltype is introduced in C++0x to simplify deducing type.
1. For the question above, here is one solution using decltype:
struct generic_plus {
template <typename T, typename U>
auto operator()(T t, U u) const -> decltype(t + u) {
return t + u;
}
};
int main()
{
int arr1[] = {1, 2, 3};
double arr2[] = {2.1, 3.2, 4.3};
vector<int> v1(arr1, arr1 + sizeof(arr1) / sizeof(arr1[0]));
vector<double> v2(arr2, arr2 + sizeof(arr2) / sizeof(arr2[0]));
generic_plus f;
transform(v1.begin(), v1.end(), v2.begin(),
ostream_iterator<decltype(f(v1[0], v2[0]))>(cout, " "), f);
}
2. decltype is a type specifier. So it can appear in any place where a type can.
decltype(0) *p = 0; // int *
const decltype(0) r = *p; // const int
Comparison between auto and decltype
These two features are similar. To distinguish them from each other, here are some differences:
• auto is used to declare variable, decltype is used to deduce type.
• The expression in decltype will never be evaluated, so it can’t be used on lambda.
• The rules are quite different (the complete rule for decltype can be found in n3090.pdf, 7.1.6.2/4)
• The type of the variable declared by auto is the type of the initializer.
• For identifier or class member access without parenthesis, decltype returns the type of the underlying entity.
• For function call, decltype returns the return type of the function.
• Otherwise, for lvalue expression, decltype returns T& while it returns T for rvalue expression (T is the type of the expression).
Known issues / limitations
1. Because decltype will return T& for lvalue expression, you have to remove the reference if necessary. Some examples:
struct A {int x;};
int f();
void test()
{
const A *pa = 0;
int i = 0;
int *pi = &i;
decltype(i); // int, type of i
decltype((i)); // int &, i is lvalue expression
decltype((i + i)); // int, i + i is rvalue expression
decltype(pa->x); // int, type of A::x
decltype((pa->x)); // const int &, pa->x is lvalue expression
decltype(f()); // int, return type of f
decltype(*pi); // int &, *pi is lvalue expression
std::remove_reference<decltype(*pi)>::type; // int, remove unnecessary reference
}
2. According to the newest draft n3090.pdf 10/1, decltype can appear in the base class list. However, it is too late for VC2010 to follow this change. For example, the following code won’t compile in VC2010 and GCC 4.3.4
struct A {} a;
struct B : decltype(a) {};
Known bugs in VC2010
There are some bugs related to decltype. The main reason is that the internal representation of the expression is quite complex and not very suitable for type deduction (it is OK for sizeof)
I've listed them in a separate post.