The C++ type system is complicated. When type deduction rules are added to the mix (e.g. in function templates or when variables are declared with type auto), the results may be unexpected to those who are not entirely familiar with how types are deduced in such contexts. However, there are a few tools which can be used to alleviate the pain of the desperate developer. One of my favorite ones is a type trait called std::is_same. Given two types T1 and T2, std::is_same<T1,T2> is a struct containing a boolean member value which is equal to true if T1 and T2 are the same type up to their const-volatile qualifications, otherwise false:
#include <iostream> #include <string> #include <type_traits> int main() { using namespace std; /* print boolean values as "true"/"false" instead of 1/0 */ cout << boolalpha; /* true, false, false */ cout << is_same<int, int>::value << "\n"; cout << is_same<int, const int>::value << "\n"; cout << is_same<int, char>::value << "\n"; /* false, false */ cout << is_same<int, int&>::value << "\n"; cout << is_same<int, int&&>::value << "\n"; auto x1 = string("Hello, world!"); auto& x2 = x1; auto&& x3 = move(x1); /* false, false, false */ cout << is_same<decltype(x1), decltype(x2)>::value << "\n"; cout << is_same<decltype(x2), decltype(x3)>::value << "\n"; cout << is_same<decltype(x3), decltype(x1)>::value << "\n"; /* true, true, true */ cout << is_same<decltype(x1), string>::value << "\n"; cout << is_same<decltype(x2), string&>::value << "\n"; cout << is_same<decltype(x3), string&&>::value << "\n"; return 0; }
I apologize if I spoiled your learning experience by adding the results of the print statements above. But in any case, I hope these results show you how useful std::is_same is if you are trying to investigate the type which the compiler assigns for a given variable or template parameter.
You may be asking yourself why can't we simply use typeid to determine types. Sadly, the reason is due to the fact that typeid is far less precise than std::is_same for this purpose. To see that, let us convert the code above to its equivalent using typeid (notice that the header typeinfo is now included instead of type_traits):
#include <iostream> #include <string> #include <typeinfo> int main() { using namespace std; /* print boolean values as "true"/"false" instead of 1/0 */ cout << boolalpha; /* true, true(!), false */ cout << (typeid(int) == typeid(int)) << "\n"; cout << (typeid(int) == typeid(const int)) << "\n"; cout << (typeid(int) == typeid(char)) << "\n"; /* true(!), true(!) */ cout << (typeid(int) == typeid(int&)) << "\n"; cout << (typeid(int) == typeid(int&&)) << "\n"; auto x1 = string("Hello, world!"); /* x1 is an std::string */ auto& x2 = x1; /* x2 is an std::string& */ auto&& x3 = move(x1); /* x3 is an std::string&& */ /* true(!), true(!), true(!) */ cout << (typeid(x1) == typeid(x2)) << "\n"; cout << (typeid(x2) == typeid(x3)) << "\n"; cout << (typeid(x3) == typeid(x1)) << "\n"; /* true, true, true */ cout << (typeid(x1) == typeid(string)) << "\n"; cout << (typeid(x2) == typeid(string&)) << "\n"; cout << (typeid(x3) == typeid(string&&)) << "\n"; return 0; }
Uh... surprise, surprise?! If the results above are bewildering to you, that's because typeid is a poor tool for determining types and you are not (yet!) aware of it. The reason for the "poor" on my previous sentence is simple: typeid ignores the "referenceness", the "constness" and the "volatileness" of the type or expression passed to it. Take a look at the example just shown: you will see that this is exactly the reason why the results of the type comparisons are what they are. Shocking as this may be, this is actually specified in the C++ language (for more on this topic, see [1], items 1 and 4).
As a final note, an object of type std::is_same can be implicitly converted to bool, so you can default-initialize such an object and print its value directly. This will require less typing and result in cleaner code:
#include <iostream> #include <type_traits> int main() { using namespace std; /* print boolean values as "true"/"false" instead of 1/0 */ cout << boolalpha; /* true, false, false */ cout << is_same<int, int>() << "\n"; cout << is_same<int, const int>() << "\n"; cout << is_same<int, char>() << "\n"; return 0; }
Additional references
[1] | Scott Meyers, Effective Modern C++, O'Reilly Media; 1st edition (2014) |
Comments
No comments posted yet.