在模板方法 c++ 中传递结构参数

Passing struct parameter in template method c++

本文关键字:结构 参数 模板方法 c++      更新时间:2023-10-16

我在将参数传递给类模板时遇到问题

struct Car {
int id;
char *model;
int date;
int cost;
};
template <class T>
class Set
{
private:
int *a;
int _size;
map<int, T> data;
public:
//some methods before
void insert(T x)
{
int num;
if (std::is_same<T, Car>::value)
num = x.id;
if (num >= 0 && num < (_size << 5))
throw "Element is out of set size!";
a[num / 32] = a[num / 32] | (1 << (num % 32));
if (std::is_same<T, Car>::value)
data.insert(make_pair(num, x));
}
};

我的insert方法应该接受int类型和Car结构。但是Visual Studio说我在num = x.id;有编译错误,x应该是一个类,结构或联合。我可以传递指向此函数的指针,但我将无法传递像class.insert(5)这样的整数。我如何解决它或如何使方法接受指向结构体和常规变量的指针,而无需对其中一种类型进行另一个规范?

是的,这是很多人都在努力解决的问题。让我们看看截取的模板方法:

void insert(T x)
{
int num;
if (std::is_same<T, Car>::value)
num = x.id;
//...

这里的问题是if语句在运行时进行语义计算(即使编译器可以优化分支,因为它会在编译时知道值(,因此,编译器需要"假装"为两个分支生成代码。附带说明一下,当分支未被占用时,您的num将保持单元化,这是未定义的行为。

在您的情况下,这意味着即使T不是Car,语义上仍然需要为num = x.id生成代码。显然,int.id不是一个有效的说法。

要使用 C++17 解决此问题,您可以使用if constexpr

if constexpr (std::is_same<T, Car>::value) //...

Constexpr if保证在编译时进行评估,并且不会计算属于未获取分支的代码。

在 C++17 之前的世界中,您通常可以使用标签调度或 SFINAE。我总是更喜欢标签调度,因为我认为它比 SFINAE 更简单。标签调度依赖于使用函数重载,在您的情况下是这样的(因为不清楚您的代码在传递时会做什么int,我会假装您需要将num设置为 int passed:

int get_id(const Car& car) {
return car.id;
}
int get_id(int v) {
return v;
}
void insert(const T& t) {
int num = get_id(t);
// ...

请注意,在这种特殊情况下,它甚至不是标签调度,因为情况非常简单。

考虑这个简化的例子:

template <typename T> 
struct foo { 
T t;
void bar() {
if (std::is_same<T,Car>) { std::cout << t.id; }
}
};

现在你必须记住,模板实例化发生在编译时,即编译器创建foo<Car>

struct foo<Car> {  
Car t;
void bar() {
if (true) { std::cout << t.id; }
}
};

到目前为止一切顺利,但是当您为int实例化相同的模板时,您会得到

struct foo<int> {  
int t;
void bar() {
if (false) { std::cout << t.id; }
}
};

问题是,即使条件始终为 false,代码仍然需要编译。int没有id,因此您的代码无法编译。

有几种方法可以解决您的问题。您可以使用 SFINAE(如果您不习惯它,可能会有点混乱,就像我一样;)、if constexpr(避免未采用的分支必须是有效的(或只是明确提供您需要的专业化。

为了使该行甚至编译,您必须使用if constexpr

if constexpr(std::is_same<T, Car>::value) num = x.id;

但请注意,从您当前的代码开始,否则num仍然未初始化(但仍在下一行中使用(。

如果你想能够insertTCarint类型的参数,最合理的方法可能是在三个重载之间分散功能:

void insert(T x);
void insert(Car x);
void insert(int x);

并且另外将模板专用于两种类型Carint,否则将无法编译。

或者你的意思是Set实际上应该接受所有类型的元素?然后它是一个更棘手的类型。首先,insert方法本身应该是模板化的:

template<typename T> void insert(T x);

而类本身很可能不应该:

class Set;

(然后在模板化insert您可以使用此线程中有关if constexpr

的所有内容。接下来,底层容器应该接受所有类型的值,这意味着你需要使用类似map<int, std::any>的东西(顺便说一句,为什么map?如果您存储与整数混合的汽车,则按 ID 排序可能不是很一致。你可以在这里使用unordered_map,甚至可以unordered_set使用你自己的多类型哈希函数。

最后但并非最不重要的一点是,需要一段时间才能制定一个最小一致的方案来实际检索存储的值。这里的主要问题是,你必须以类型擦除的方式存储它们,所以你需要以某种方式调用存储值的实际类型 - 以一种并非完全不优雅的方式。

这就是您要构建的吗?