类模板实例化中的类型转换

Type conversion in class template instantiation

本文关键字:类型转换 实例化      更新时间:2023-10-16

我有一个模板类item,它存储各种类型的对象T。它还在实例化/初始化中将属性附加到这些对象。

我想实现的一件特别的事情是,每当item看到一个const char *时,它就会将其视为std::string并存储。可以这样做,如下所示。

但是在类型检查中,我发现从const char *实例化的item在类型上仍然与从std::string实例化的item不同。请参阅最后一行带有注释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
}

我的问题是:是否可以对模板类item进行任何更改,以便从const char *实例化的item在类型上与从std::string实例化的item相同?

换句话说,我可以对模板类item的设计进行任何更改,以便typeid(a) == typeid(b)的计算结果为 true 吗?

谢谢!

注意:这是之前关于模板功能的问题的后续。但我认为有一些本质上不同的东西,值得一个独立的问题。

编辑:我的目标是更改模板类item的设计(例如item签名(,而不是main中的代码,假定由用户提供。我想让item用户的生活更轻松,不要求他们在实例化中显式提供类型T。这可以通过 C++17 模板类参数推导或一些等效的解决方法来完成。

更新:谢谢大家!特别感谢@xskxzr,他的一句话完全解决了我的问题。使用用于类模板参数推理的用户定义演绎指南,我甚至不需要以前的代码中的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 !
}

您可以添加用户定义的扣除指南:

item(const char *) -> item<std::string>;

有了这个演绎指南,a将被推导出为item<std::string>

不可以,您不能直接使使用不同模板参数的两个模板化对象的 typeid 相同。

但是为了实现您的最终目标,您可以使用类似工厂的模式。它可能看起来像这样:

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);
} 

这种方法的缺点是,您需要使用此工厂构造所有对象。如果你有很多例外,你需要为每个例外进行专业化。

这与其说是答案,不如说是猜测,但我会说不。 模板在编译时展开,因此因为您正在创建一个

item<const char*>

和一个

item<std::string>

然后扩展的代码看起来像

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;
}
};

(或多或少;它们实际上不会被称为item1和item2(

您选择如何评估这两种类型取决于您,但对于编译器来说,它们实际上是两种不同的类型。

好吧,我以前从未见过或使用 std::condition,所以我不确定它在做什么,但在阅读它并玩弄你的代码后,我确实通过使用它让它"工作">

bar<T>

作为模板类型。 所以而不是

auto a = item<const char*>("const char *");
auto b = item<string>(string("string"));

我做了

auto a = item<bar<const char*>>("const char *");
auto b = item<bar<string>>(string("string"));

问题是在这两种情况下,您需要模板类型相同,这意味着在扩展模板之前,类型需要解析为 std::string。 只要使用条件,就可以定义任何类型。

auto c = item<bar<int>>(5);

不确定这是一个很好的解决方案(这就是为什么我说"工作"(,但看看我关于类类型实际上不同的另一个答案。