T = class Widget const *
param = class Widget const *
Three independent compilers producing the same information suggests that the information is accurate. But look more closely. In the template f
, param
's declared type is const T&
. That being the case, doesn't it seem odd that T
and param
have the same type? If T
were int
, for example, param
's type should be const int&
— not the same type at all.
Sadly, the results of std::type_info::name
are not reliable. In this case, for example, the type that all three compilers report for param are incorrect. Furthermore, they're essentially required to be incorrect, because the specification for std::type_info::name
mandates that the type be treated as if it had been passed to a template function as a by-value parameter. As Item 1explains, that means that if the type is a reference, its reference-ness is ignored, and if the type after reference removal is const
(or volatile
), its const
ness (or volatile
ness) is also ignored. That's why param
's type — which is const Widget * const &
— is reported as const Widget*
. First the type's reference-ness is removed, and then the const
ness of the resulting pointer is eliminated.
Equally sadly, the type information displayed by IDE editors is also not reliable — or at least not reliably useful. For this same example, one IDE editor I know reports T
's type as (I am not making this up):
const
std::_Simple_types
std::allocator >::_Alloc>::value_type>::value_type *
The same IDE editor shows param
's type as:
const std::_Simple_types<...>::value_type *const &
That's less intimidating than the type for T
, but the “ ...
” in the middle is confusing until you realize that it's the IDE editor's way of saying “I'm omitting all that stuff that's part of T
's type.” With any luck, your development environment does a better job on code like this.
If you're more inclined to rely on libraries than luck, you'll be pleased to know that where std::type_info::name
and IDEs may fail, the Boost TypeIndex library (often written as Boost.TypeIndex ) is designed to succeed. The library isn't part of Standard C++, but neither are IDEs or templates like TD. Furthermore, the fact that Boost libraries (available at boost.com ) are cross-platform, open source, and available under a license designed to be palatable to even the most paranoid corporate legal team means that code using Boost libraries is nearly as portable as code relying on the Standard Library.
Here's how our function f can produce accurate type information using Boost.TypeIndex:
#include
template
void f(const T& param) {
using std::cout;
using boost::typeindex::type_id_with_cvr;
// show T
cout << "T = "
<< type_id_with_cvr().pretty_name()
<< '\n';
// show param's type
cout << "param = "
<< type_id_with_cvr().pretty_name()
<< '\n';
…
}
The way this works is that the function template boost::typeindex::type_id_with_cvr
takes a type argument (the type about which we want information) and doesn't remove const
, volatile
, or reference qualifiers (hence the “ with_cvr
” in the template name). The result is a boost::typeindex::type_index
object, whose pretty_name
member function produces a std::string
containing a human-friendly representation of the type.
With this implementation for f
, consider again the call that yields incorrect type information for param
when typeid
is used:
std::vector createVec(); // factory function
const auto vw = createVec(); // init vw w/factory return
if (!vw.empty()) {
f(&vw[0]); // call f
…
}
Under compilers from GNU and Clang, Boost.TypeIndex produces this (accurate) output:
T = Widget const*
param = Widget const* const&
Results under Microsoft's compiler are essentially the same:
T = class Widget const *
param = class Widget const * const &
Such near-uniformity is nice, but it's important to remember that IDE editors, compiler error messages, and libraries like Boost.TypeIndex are merely tools you can use to help you figure out what types your compilers are deducing. All can be helpful, but at the end of the day, there's no substitute for understanding the type deduction information in Items 1– 3.
Things to Remember
• Deduced types can often be seen using IDE editors, compiler error messages, and the Boost TypeIndex library.
• The results of some tools may be neither helpful nor accurate, so an understanding of C++'s type deduction rules remains essential.
In concept, auto
is as simple as simple can be, but it's more subtle than it looks. Using it saves typing, sure, but it also prevents correctness and performance issues that can bedevil manual type declarations. Furthermore, some of auto
's type deduction results, while dutifully conforming to the prescribed algorithm, are, from the perspective of a programmer, just wrong. When that's the case, it's important to know how to guide auto to the right answer, because falling back on manual type declarations is an alternative that's often best avoided.
This brief chapter covers all of auto
's ins and outs.
Item 5: Prefer auto
to explicit type declarations.
Ah, the simple joy of
int x;
Wait. Damn. I forgot to initialize x
, so its value is indeterminate. Maybe. It might actually be initialized to zero. Depends on the context. Sigh.
Never mind. Let's move on to the simple joy of declaring a local variable to be initialized by dereferencing an iterator:
template // algorithm to dwim ("do what I mean")
void dwim(It b, It e) // for all elements in range from
{ // b to e
while (b != e) {
typename std::iterator_traits::value_type
currValue = *b;
…
}
}
Ugh. “ typename std::iterator_traits::value_type
” to express the type of the value pointed to by an iterator? Really? I must have blocked out the memory of how much fun that is. Damn. Wait — didn't I already say that?
Okay, simple joy (take three): the delight of declaring a local variable whose type is that of a closure. Oh, right. The type of a closure is known only to the compiler, hence can't be written out. Sigh. Damn.
Damn, damn, damn! Programming in C++ is not the joyous experience it should be!
Well, it didn't used to be. But as of C++11, all these issues go away, courtesy of auto
. auto
variables have their type deduced from their initializer, so they must be initialized. That means you can wave goodbye to a host of uninitialized variable problems as you speed by on the modern C++ superhighway:
Читать дальше