在模板方法c ++

时间:2018-05-22 13:20:42

标签: c++ class templates

我将参数传递给类模板

时遇到问题
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)之类的整数。我如何解决它或如何使方法接受结构和常规变量的指针而不对其中一个类型进行另一个规范?

3 个答案:

答案 0 :(得分:3)

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

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传递:< / p>

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);
    // ...

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

答案 1 :(得分:1)

考虑这个简化的例子:

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; }
     }
 };
问题是即使条件总是为假,代码仍然需要编译。 int没有id,因此您的代码无法编译。

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

答案 2 :(得分:-1)

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

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

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

最合理的方法是,如果您希望能够insert类型TCarint的{​​{1}}参数,可能会在三者之间传播功能重载:

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>之类的东西(BTW,为什么map?订购如果你存储混合了整数的汽车,-ids可能不是很一致。你可以在这里使用unordered_map,甚至unordered_set,使用你自己的多重散列函数。)

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

这是你想要建立的吗?