Page 8 - HRM-00-v1
P. 8
LANGUAGE FEATURES
How to Avoid Template Type Deduction in C++
First off, once we know Key we can deduce type_identity_t<Key>: it is Key, by definition. But what makes it useful is that the deduction doesn’t work the other way around: if the compiler knows Key, it doesn’t try to deduce type_identity_t<Key>.
Let’s now test them with the following calling code, forstd::map to start:
auto myMap = std::map<std::string, int>{ {“one”, 1}, {“two”, 2}, {“three”, 3} }; replace_key(myMap, “two”, «dos”);
What we get is a compilation error:
main.cpp: In function ‘int main()’:
main.cpp:35:32: error: no matching function for call to ‘replace_key(std::map<std
::__cxx11::basic_string<char>, int>&, const char [4], const char [4])’ replace_key(myMap, “two”, “dos”);
^
main.cpp:7:6: note: candidate: ‘template<class Key, class Value> void replace_
key(std::map<Key, Value>&, const Key&, const Key&)’ void replace_key(std::map<Key, Value>& container,
or even a std::map<int, std::string>. But our generic function has to work with all types.
One way out is to work around the implicit conversion:
auto myMap = std::map<std::string, int>{ {“one”, 1}, {“two”, 2}, {“three”, 3} }; replace_key(myMap, std::string(“two”), std::string(«dos”));
Yuck! This doesn’t look natural at all. Can’t we do better?
Using type_identity
C++20 will define a new template type in the standard library: std::type_identity. This is the identity function in template metapro- gramming. Or put another way, this is the template type defined by
template<typename T> struct type_identity {
using type = T; };
It comes with C++20’s standard library, but it was implementable since C++98 (by using atypedefinstead ofusing).
Like all metaprogramming functions, std::type_identity comes with its _t counterpart, to avoid the noisy typename and ::type in calling code:
template< class T >
using type_identity_t = typename type_identity<T>::type;
We can use type_identity to prevent the deduction of some template parameters (thanks to Walletfox for showing me this technique!):
template<typename Key, typename Value>
void replace_key(std::map<Key, Value>& container,
const type_identity_t<Key>& oldKey, const type_identity_t<Key>& newKey)
{
auto node = container.extract(oldKey); if(!node.empty())
^~~~~~~~~~~ main.cpp:7:6: note:
template argument deduction/substitution failed: deduced conflicting types for parameter ‘const Key’
main.cpp:35:32: note: (‘std::__cxx11::basic_string<char>’ and ‘char [4]’)
replace_key(myMap, “two”, “dos”); ^
main.cpp:20:6: note: candidate: ‘template<class Key> void replace_key(std:: set<Key>&, const Key&, const Key&)’
void replace_key(std::set<Key>& container, ^~~~~~~~~~~
main.cpp:20:6: note: main.cpp:35:32: note:
template argument deduction/substitution failed: ‘std::map<std::__cxx11::basic_string<char>, int>’ is not
derived from ‘std::set<Key>’ replace_key(myMap, “two”, “dos”);
^
The problem is that “two” and “dos” are of type char const *, which is implicitly convertible to std::string. But there is no implicit con- version in the context of template type deduction (like we saw when handling multiple types with std::max). So Key is deduced as being of type char const *.
On the other hand, Container is deduced to be of type std::map<std::string, int> because this is the type of myMap. Because of this inconsistency in the deduced types ofKey, the template genera- tion fails, so the compiler doesn’t find a replace_key function.
Note that we wouldn’t have encountered this problem if we didn’t rely on an implicit conversion—for example, if we had a std::map<int, int>
8 | Human Readable Magazine