【问题标题】:Why does the compiler seem to ignore if statements in templated classes?为什么编译器似乎忽略了模板类中的 if 语句?
【发布时间】:2014-05-23 02:47:12
【问题描述】:

首先是代码

vector<T> kvec;

for (ulong kv = 0; kv < ke.count; kv++)
{
    T key;
    if (typeid(T) != typeid(QUAT32))
    {


        fread(&key, sizeof(T), 1, fh);
    }
    else{

        shortQuat key16;

        fread(&key16, sizeof(shortQuat), 1, fh);
        key.Parse(key16);

    }
    kvec.push_back(key);
}

key.Parse(key16); 处抛出以下编译器错误:

error C2039: 'Parse' : is not a member of 'libm2::VEC3F'

当然不是;但问题是任何具有 VEC3F 类型的东西都不应该到达那里。显然,编译器正在使用第一种发送到 sn-p 所在类的类型,这很好 - 但为什么要忽略条件呢?

是的,如果我注释掉那一行,它编译得很好——并且条件在运行时工作得很好。

所以我想真正的问题是,处理这个问题的最佳方法是什么,而不必为一种专门的类型复制整个类?

【问题讨论】:

  • if 在运行时运行,而不是在编译时运行。改用模板特化(将代码放在模板函数中,并将该函数特化为QUAT32)。
  • 我很害怕——但我不想为了一个小而罕见的情况而复制一大段代码。所以我想出了一个骇人听闻的解决方案。将在下面发布。
  • 我不确定我看到重复的来源。只需将 if 块和 else 块中的代码移动到两个函数中(并适当地调用它们)。
  • 这是否意味着编译器可以走多远?因为即使我将其分叉到模板函数中,我仍然需要在某个地方进行类型比较。
  • 我会在几分钟内提供一些代码。它应该消除任何混乱:-)

标签: c++ visual-studio


【解决方案1】:

扩展我的评论:

if 在运行时运行,而不是在编译时运行(优化器可能会删除分支,但它必须首先进行编译才能达到这一目标)。

您可以改用模板特化,这是在编译时完成的(编译器会为您提供的每种类型实例化模板函数的单独副本在编译它之前 )。您可以提供专门化来为特定类型强制使用不同的(特殊)函数体,这正是您在这种情况下想要的。

这是从您的问题中的代码派生的示例(我假设非 QUAT32 的 double-push_back 到 kvec 是一个错字):

template<typename T>
inline void readKey(T& key, FILE* fh)
{
    fread(&key, sizeof(T), 1, fh);
}

template<>
inline void readKey<QUAT32>(QUAT32& key, FILE* fh)
{
    shortQuat key16;
    fread(&key16, sizeof(shortQuat), 1, fh);
    key.Parse(key16);
}

// ...
vector<T> kvec;
for (ulong kv = 0; kv < ke.count; kv++)
{
    T key;
    readKey(key, fh);
    kvec.push_back(key);
}

【讨论】:

  • 请注意,您还可以更改 readKey 以返回它在本地构造的 T 的副本(而不是传入引用);这可能会使循环长一行(但调用readKey 时会丢失类型推断)。
  • 那是……哇。那么这个逻辑是如何运作的呢?我的意思是,我意识到第二个重载没有在模板中设置类型名 T - 所以我想这就是允许在不指定类型的情况下调用该方法的原因 - 但是该方法指定 QUAT32 ......那么 readkey()变成 readkey()?是的,这是一个错字修正。
  • @Digital:C++ 的一个特性或多或少正是为此目的而设计的 :-) 在这种情况下,没有实际的函数重载,只是 模板特化 (尽管您也可以将重载与模板混合使用)。第一个是所有类型 T 的一般情况模板。第二个特化特定类型的一般情况——编译器看到template&lt;&gt; 并将其添加到它的可能函数表中用那个名字。当需要实例化函数(调用它的地方)时,它会选择一个可用的特化,然后回退到默认模板。
  • @Digital: readKey 是 readKey&lt;T&gt;,但是由于C++的另一个特性,模板类型推断,我们可以省略当我们调用它时的类型参数,编译器会从传入的参数的类型中找出它(如果可能的话,在这种情况下就是这样)。
  • 非常感谢您花时间解释!这实际上很酷。
【解决方案2】:

问题: 需要处理模板化构造函数中的特殊情况,需要对其进行转换以使其有用。但是,正如我所担心的,if 语句在编译时会被忽略。

解决方案:

向模板添加第二种类型,并在其中一种类型中创建类型转换运算符。

vector<T> kvec;
for (ulong kv = 0; kv < ke.count; kv++)
{
    T2 key;
    fread(&key, sizeof(T2), 1, fh);
    kvec.push_back((T)key);
}

在大多数情况下,类的构造如下:

SomeClass c = SomeClass<typeA,typeA>(fh);

显然,在同一类型之间进行转换应该非常简单。但在 QUAT32 的情况下...

SomeClass c = SomeClass<QUAT32,SHORT_QUAT>(fh);

解决方案就是这样做:

typedef struct SHORT_QUAT
{
    short x;
    short y;
    short z;
    short w;
    operator const QUAT32(){
        QUAT32 q;
        q.x = float(x < 0 ? x + 32768 : x - 32767) / 32767.0f;
        q.y = float(y < 0 ? y + 32768 : y - 32767) / 32767.0f;
        q.z = float(z < 0 ? z + 32768 : z - 32767) / 32767.0f;
        q.w = float(w < 0 ? w + 32768 : w - 32767) / 32767.0f;
        return q;
    }
}shortQuat;

然后以 shortquat 形式存储的数据可以毫无问题地转换为更有用的浮点格式。

仅仅为了一个特殊情况而必须创建一个完整的副本似乎是一种浪费。我想这是两个丑陋中较小的一个。

【讨论】:

    【解决方案3】:

    另一种解决方案是在模板类中声明静态变量,该变量存储之前定义的类型ID,并在需要时检查它。

    #define CNTYPE_NONE     0
    #define CNTYPE_STRING   1
    #define CNTYPE_SIGNINT  2
    
    template <typename key_t, typename value_t>
    class HashTable 
    {
    public:
        HashTable() {;}
        ...................
        static int m_nTypeID;
        // and later you can check the type by:    
    
        void SomeFunc()
        {
            if(m_nTypeID == CNTYPE_STRING) 
            {
                ................
            } 
            else 
            {
                ................
            }
        }
    };
    
    typedef HashTable<std::string, std::string> THashTableString;
    typedef HashTable<int, int> THashTableSignInt;
    
    THashTableString::m_nTypeID = CNTYPE_STRING;
    THashTableSignInt::m_nTypeID = CNTYPE_SIGNINT;
    
    int main()
    {
        THashTableString strTable;
        THashTableSignInt intTable;
        .........................................        
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2013-09-15
      • 2014-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多