Type conversion in class template instantiation
Type conversion in class template instantiation
I have a template class item
which stores objects of various types T
. It also attaches attributes to those objects in instantiation/initialization.
item
T
One special thing I want to achieve is that whenever item
sees a const char *
, it deems and stores it as a std::string
. This could be done, as follows.
item
const char *
std::string
But in type checking, I found an item
instantiated from a const char *
is still different in type from an item
instantiated from a std::string
. Please see the last line with comment false
, which I want to make true
.
item
const char *
item
std::string
false
true
#include <iostream>
#include <string>
#include <type_traits>
using namespace std;
template<typename T>
using bar = typename std::conditional<std::is_same<T, const char *>::value,
string, T>::type;
template<typename T>
class item
bar<T> thing;
// other attributes ...
public:
item(T t) : thing(t)
// other constructors ...
bar<T> what() const
return thing;
;
int main()
auto a = item("const char *"); // class template argument deduction (C++17)
auto b = item(string("string")); // class template argument deduction (C++17)
cout << std::boolalpha;
cout << (typeid(a.what()) == typeid(b.what())) << endl; // true
cout << (typeid(a) == typeid(b)) << endl; // false
My question is: is it possible to make any change to the template class item
so that an item
instantiated from a const char *
becomes the same in type with an item
instantiated from a std::string
?
item
item
const char *
item
std::string
In other words, can I make any change to the design of the template class item
so that typeid(a) == typeid(b)
evaluates to true ?
item
typeid(a) == typeid(b)
Thank you !
Note: This follows up a previous question on template function. But I think there's something intrinsically different that it deserves a stand-alone question.
Edit: My goal is to change the design of the template class item
(e.g. item
signatures), not the code in main
, which is assumed to be supplied by users. I want to make life easier for the users of item
, by not asking them to explicitly supply type T
in instantiation. This is meant to be done by C++17 template class argument deduction or some equivalent workarounds.
item
item
main
item
T
Update: Thank you all! Special thanks to @xskxzr, whose one-liner exactly solves my question. With user-defined deduction guides for class template argument deduction, I don't even need the bar<T>
technique in my previous code. I put updated code below for your comparison.
bar<T>
#include <iostream>
#include <string>
using namespace std;
template<typename T>
class item
// UPDATE: no bar<T> needed any more
T thing;
// other attributes ...
public:
item(T t) : thing(t)
// other constructors ...
// UPDATE: no bar<T> needed any more
T what() const
return thing;
;
item(const char *) -> item<std::string>; // UPDATE: user-defined deduction guide !
int main()
auto a = item("const char *"); // class template argument deduction (C++17)
auto b = item(string("string")); // class template argument deduction (C++17)
cout << std::boolalpha;
cout << (typeid(a.what()) == typeid(b.what())) << endl; // true
cout << (typeid(a) == typeid(b)) << endl; // UPDATE: now true !
Thanks for the question. For some further work. To be specific, I want to have a template class
container
that contains item<T>
with the same type T
. And I want one container
to hold many objects initialized from either const char *
or std::string
. One workaround may be to use a heterogeneous container like boost::any
, but I want to see if there are alternatives.– fleix
Sep 9 '18 at 3:52
container
item<T>
T
container
const char *
std::string
boost::any
4 Answers
4
You can add a user-defined deduction guide:
item(const char *) -> item<std::string>;
With this deduction guide, a
will be deduced to be item<std::string>
.
a
item<std::string>
This is the shortest and most concise answer, and it has the benefit of being correct as far as I can tell: godbolt.org/z/wLTrOL
– bobshowrocks
Sep 9 '18 at 5:09
Case closed! Thank you very much. This is exactly what I've been looking for!
– fleix
Sep 9 '18 at 5:12
Nice! There are a lot of features in C++11/17 that I'm still not aware of !
– Moohasha
Sep 9 '18 at 12:26
No, you can't directly make the typeid of two templated objects using different template arguements be the same.
But to achieve your end goal you can use a factory like pattern. It could look something like this:
template<typename T, typename R = T>
item<R> make_item(T&& t)
return item<T>(std::forward<T>(t));
// Specialization for const char *
template<>
item<std::string> make_item(const char *&& str)
return item<std::string>(str);
The downside with this approach is that you'll need to construct all of your objects with this factory. And if you have a lot of exceptions you'll need to make a specialization for each exception.
I think this could be one solution! I'll try it.
– fleix
Sep 9 '18 at 4:26
Upon further examination this doesn't work as is. Please check out xskxzr's answer.
– bobshowrocks
Sep 9 '18 at 5:09
This is more a guess than an answer, but I'd say no. Templates are expanded at compile time, so because you are creating an
item<const char*>
and an
item<std::string>
then the code that gets expanded looks something like
class item1
bar<const char*> thing;
// other attributes ...
public:
item(const char* t) : thing(t)
// other constructors ...
bar<const char*> what() const
return thing;
;
class item2
bar<std::string> thing;
// other attributes ...
public:
item(std::string t) : thing(t)
// other constructors ...
bar<std::string> what() const
return thing;
;
(More or less; they wouldn't actually be called item1 and item2)
How you chose to evaluate these two types is up to you, but to the compiler they are in fact two different types.
Ok, I'd never seen or used std::conditional before so I wasn't sure what that was doing, but after reading up on it and playing around with your code I did get it to "work" by using
bar<T>
as the template type. So instead of
auto a = item<const char*>("const char *");
auto b = item<string>(string("string"));
I did
auto a = item<bar<const char*>>("const char *");
auto b = item<bar<string>>(string("string"));
The thing is you need the template type to be the same in both cases, meaning the type needs to resolve to std::string before the template gets expanded. As long as you use your conditional, you can define any type.
auto c = item<bar<int>>(5);
Not sure that's a good solution (which is why I said "work"), but see my other answer about the class types actually being different.
Thanks. Your code should work as you expect. But what I want is change the design of template class
item
. The code in main is meant to be supplied by users of item
.– fleix
Sep 9 '18 at 3:56
item
item
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Why do you need the typeid's to be the same?
– bobshowrocks
Sep 9 '18 at 3:46