【问题标题】:select an union member depending on a template parameter根据模板参数选择联合成员
【发布时间】:2012-12-31 18:49:14
【问题描述】:

我正在处理 C++ 中的联合,我想要一个函数模板,它可以根据模板参数访问活动的联合成员。

代码类似于(doSomething 只是一个示例):

union Union {
  int16_t i16;
  int32_t i32;
};

enum class ActiveMember {
    I16 
  , I32
}

template <ActiveMember M>
void doSomething(Union a, const Union b) {
  selectMemeber(a, M) = selectMember(b, M);
  // this would be exactly (not equivalent) the same
  // that a.X = b.X depending on T.
}

为了做到这一点,我只发现了一些不好的技巧,比如专业化,或者访问和分配的方式不统一。

我遗漏了一些东西,应该用其他方法来做这些事情吗?

【问题讨论】:

  • 为什么你认为专业化是“坏黑客”? C++ 中还没有“静态 if”之类的东西,所以除了定义单独的模板之外,我没有看到任何其他方法。不过我可能是错的
  • 我猜你的意思是selectMemeber(a, M) = selectMember(b, M);
  • @Philipp,是的,谢谢!已编辑。
  • 我认为通过指向成员的指针可能会有一个很好的解决方案,但我还不确定这是否合法。见stackoverflow.com/questions/14375965/…

标签: c++ templates member unions


【解决方案1】:

我能想到的唯一解决方案是将运算符添加到联合中:

union Union
{
    char c;
    short s;
    int i;
    float f;
    double d;

    operator char() const { return c; }
    operator short() const { return s; }
    operator int() const { return i; }
    operator float() const { return f; }
    operator double() const { return d; }
    template <typename T> operator T() const { /* invalid conversion */ T t; return t; }

    Union &operator =(char ac) { c = ac; return *this; }
    Union &operator =(short as) { s = as; return *this; }
    Union &operator =(int ai) { i = ai; return *this; }
    Union &operator =(float af) { f = af; return *this; }
    Union &operator =(double ad) { d = ad; return *this; }
    template <typename T> Union &operator =(T at) { /* invalid asignement */ return *this; }
};

它允许您控制联合作为some type 工作时的行为:

template <typename T>
void doSomething(Union a, const Union b)
{
    // call the 'b' conversion operator and the 'a' asignment operator.
    a = static_cast<T>(b);
}

int main(int argc, char **argv)
{
    Union a, b;

    doSomething<int>(a, b);  // calls a.i = b.i
    doSomething<char>(a, b); // calls a.c = b.c

    return 0;
}

运算符的模板版本匹配无效转换。

【讨论】:

    【解决方案2】:

    可能性1

    您可以使用简单的结构来选择成员,而不是使用枚举:

    typedef short int16_t;
    typedef long int32_t;
    
    union Union {
        int16_t i16;
        int32_t i32;
    };
    
    struct ActiveMemberI16 {};
    struct ActiveMemberI32 {};
    
    template <typename M>
    void doSomething(Union& a, Union b) {
        selectMember(a, M()) = selectMember(b, M());
    
        // this would be exactly (not equivalent) the same
        // that a.X = b.X depending on T.
    }
    
    int16_t& selectMember(Union& u, ActiveMemberI16)
    {
        return u.i16;
    }
    
    int32_t& selectMember(Union& u, ActiveMemberI32)
    {
        return u.i32;
    }
    
    int main(int argc, char* argv[])
    {
        Union a,b;
        a.i16 = 0;
        b.i16 = 1;
        doSomething<ActiveMemberI16>(a,b);
        std::cout << a.i16 << std::endl;
    
        b.i32 = 3;
        doSomething<ActiveMemberI32>(a,b);
        std::cout << a.i32 << std::endl;
        return 0;
    }
    

    这需要为联合中的每个成员定义一个结构和一个 selectMember 方法,但至少您可以在许多其他函数中使用 selectMember。

    请注意,我将参数转换为引用,如果不合适,您可以调整它。

    可能性2

    通过将联合指针转换为所需的类型指针,您可以使用单个 selectMember 函数。

    typedef short int16_t;
    typedef long int32_t;
    
    union Union {
        int16_t i16;
        int32_t i32;
    };
    template <typename T>
    T& selectMember(Union& u)
    {
        return *((T*)&u);
    }
    
    template <typename M>
    void doSomething(Union& a, Union b) {
        selectMember<M>(a) = selectMember<M>(b);
    
        // this would be exactly (not equivalent) the same
        // that a.X = b.X depending on T.
    }
    
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        Union a,b;
        a.i16 = 0;
        b.i16 = 1;
    
        doSomething<int16_t>(a,b);
        std::cout << a.i16 << std::endl;
    
        b.i32 = 100000;
        doSomething<int32_t>(a,b);
        std::cout << a.i32 << std::endl;
        return 0;
    }
    

    【讨论】:

    • 我想到了可能性 2,但在我看来这是非法的。我正在阅读,看起来我错了。谢谢
    • Potatoswatter 在我的问题中提出了同样的想法;这确实是最简单的解决方案。
    • 选项 2 的字节顺序如何?如果我们将变量的指针转换为另一个,编译器不能保证它会给我们最不重要的值。不是吗?
    • @HalilKaskavalci 字节序不影响对象的地址。一个指针指向“第一个字节”,而不是“最不重要的字节”
    【解决方案3】:

    我不确定你为什么认为模板专业化是“坏 hack”,但在 C++ 中没有“静态 if”这样的东西,所以如果你想让你的编译器根据结果来区分生成的代码在编译时计算的表达式,您需要定义不同的、专门的模板版本。

    这是你如何定义它:

    #include <iostream>
    
    using namespace std;
    
    union Union {
      int16_t int16;
      int32_t int32;
    };
    
    enum class ActiveMember {
        INT16
      , INT32
    };
    
    // Declare primary template
    template <ActiveMember M>
    void doSomething(Union a, const Union b);
    
    // First specialization
    template <>
    void doSomething<ActiveMember::INT16>(Union a, const Union b)
    {
        a.int16 = b.int16;
    
        // Do what you want here...
        cout << "int16" << endl;
    }
    
    // Second specialization
    template <>
    void doSomething<ActiveMember::INT32>(Union a, const Union b)
    {
        a.int32 = b.int32;
    
        // Do what you want here...
        cout << "int32" << endl;
    }
    

    这就是你将如何使用它。

    int main()
    {
        Union u1, u2;
        u1.int32 = 0;
        u2.int32 = 0;
    
        doSomething<ActiveMember::INT16>(u1, u2);
        doSomething<ActiveMember::INT32>(u1, u2);
    
        return 0;
    }
    

    【讨论】:

    • 因为专业化不是可扩展的解决方案,在这种情况下涉及重写代码。当然对于这个例子来说可能是最好的选择(更简单)。但是因为只有一个函数和两个成员,但是如果你需要写几个函数并且有十个成员呢?
    • @user1476999:您始终可以分解代码,这样您只需要一个需要专门化的模板,例如get&lt;M&gt;(u)。然后您可以在所有其他功能中通用地使用它。
    • @user1476999:两个考虑因素。首先:selectMember 函数的返回类型是什么?由于您正在处理联合,因此返回类型将取决于 M 的值。第二:如果 C++ 支持诸如“static_if”之类的东西,那将允许在编译时评估条件(这样错误的分支甚至不会被编译),这将使事情变得更容易并且不需要专门化。不幸的是,我们目前没有该功能。有建议将其添加到下一个标准中
    • @AndyProwl 但 selectMember 可以是任何东西,而不是“函数”。例如,如果存在从联合成员类型转换为类型的安全方法,则 selectMember(u, M) 可以类似于 UnionType::type &ref{u};所以你只需要添加一些特征。或者可以是像 selectMember(u, M) u##fromMemberToMemberIdentifier(M) 这样的宏。也许不存在一种方法,但原则上这只涉及模式数学和重写(模板+宏)。当然,正如你所说,C++ 有局限性,也许我试图做的事情是不可能的。
    • @user1476999:我明白你的意思。也许菲利普答案中的可能性 2 可以满足您的需求
    猜你喜欢
    • 2017-09-11
    • 1970-01-01
    • 1970-01-01
    • 2015-03-31
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 2017-09-27
    相关资源
    最近更新 更多