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 !






Why do you need the typeid's to be the same?

– bobshowrocks
Sep 9 '18 at 3:46






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.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)