【问题标题】:c++ metaprogramming madnessC++ 元编程疯狂
【发布时间】:2010-10-23 17:39:58
【问题描述】:

考虑以下模板化数据结构

enum eContent{
    EINT = 1,
    EFLOAT = 2,
    EBOOL = 4
};

template<int>
struct Container{
    Container(){assert(false);} //woops, don't do that!
};

template<>
struct Container<EINT>{
    Container():i(123){}
    int i;
};

template<>
struct Container<EFLOAT>{
    Container():f(123.456f){}
    float f;
};

template<>
struct Container<EBOOL>{
    Container():b(true){}
    bool b;
};



<fancy macro goes here that creates me all kind of combinations including for example>
    template<>
    struct Container<EFLOAT | EBOOL>: public Container<EFLOAT>, public Container<EBOOL>{
        Container():Container<EFLOAT>(),Container<EBOOL>(){}
    };
</fancy macro>

这样我就可以定义一个像这样的变量:

Container<EINT|EFLOAT|EBOOL> myVar;

我将如何定义这个花哨的宏?

我为什么要这个? 让它成为有趣和学习元编程的原因

【问题讨论】:

  • 您是否要创建tuple type?使用枚举值表示类型而不是直接表示类型的基本原理是什么(您的最后一个示例将变为Container&lt;int, float, bool&gt;,这似乎更惯用)。此外,由于您的目标似乎是学习,因此这种方法可以让您使用可变参数模板,这是 C++0x 的一个新功能(您显然已经在使用它,因为您的代码包含 static_assert)!
  • oi - 我不知道可变参数模板 - 听起来很有趣!谢谢你的提示!
  • 即使目标只是看看能不能完成,但最好描述一下你想解决的问题,而不是你想象中的解决方案。这样,您可能会学到意想不到的好东西,可以用更好的方式解决它,例如上面提到的可变参数模板。 :) 正如@Luc 所问,这应该是一个元组类型,还是它的目标是什么?
  • 我想要一种简单的方法来为成千上万的轻量级数据对象定义特定的数据格式。

标签: c++ templates macros metaprogramming


【解决方案1】:

首先,|| 是布尔值或运算符;当你使用它时,它总是会导致1(或者true,但是true在转换为int时总是提升为1,因为在这种情况下)如果您的代码等于EINT,因此您的模板将始终实例化为Container&lt;EINT&gt;

您可能正在寻找按位或运算符|。即使这样,编译器实际上也会按位或值,所以你会得到一个7 的值,这将导致使用非专业模板,这将失败。

究竟什么你想要完成?有一些方法可以使类型足够灵活以容纳多种类型的多个数据,但是 or 运算符不会像模板参数上下文中那样做任何远程操作。

【讨论】:

  • 我更正了 ||问题中的错字。是的,它会产生 7,这正是我想要的,一个创建正确定义的宏 Container&lt;7&gt; 以及枚举值的所有其他按位组合
  • @Mat:我认为乔纳森是对的,您应该尝试使用值类型而不是容器类型来尝试您想要做什么。如果您的问题包含您想要完成的内容,这将有所帮助。
  • 假设我想为图形处理定义很多不同类型的 VertexData。有些可以有 uv 坐标,有些可以有多个 uv 坐标,有些可以有双切线,有些可以有顶点颜色等等。根据应用程序,需要一种不同的轻量级 vertexData 来组合其中一些属性,我不想手动定义所有可能的组合
【解决方案2】:
enum eContent{
    eInt    = 1,
    eFloat  = 2,
    eBool   = 4
};

template<unsigned, unsigned>
struct Member {};

template<>
struct Member<eInt, eInt>{
    Member():i(123){}
    unsigned i;
};

template<>
struct Member<eFloat, eFloat>{
    Member():f(123.456f){}
    float f;
};

template<>
struct Member<eBool, eBool>{
    Member():b(true){}
    bool b;
};

template< unsigned members >
struct Container
    : Member< members & eInt, eInt >
    , Member< members & eFloat, eFloat >
    , Member< members & eBool, eBool >
{};

int main()
{
    Container< eFloat | eBool > c;
    c.f;    // OK
    c.b;    // OK
    c.i;    // !Nah
}

但我认为这对任何事情都没有好处,真的,它只是解决你所说的字面问题。

如果您有一些真正的问题(您认为这可能是一个解决方案),请尝试询问。

当然,除非只是玩耍或家庭作业。 :-)

干杯,

PS:作为一个良好的 C++ 编程习惯,为宏保留全部大写的名称,并且仅用于宏。这样可以避免许多潜在的名称冲突。对常量使用全大写是 Java/Python/等。约定,在某种程度上适合那些语言,但绝对不适合 C++。它源于早期的 C,其中常量必须表示为宏。 ALL UPPERCASE 曾经(并且现在)用于宏,而不是常量——好吧,除了 Brian Kernighan,但我们不要深入研究历史...... ;-)

【讨论】:

  • aaah - 我花了一点时间才明白,但这很酷!但是如果有一个构造函数为每个成员获取相同类型的参数,我将如何调用 baseConstructor。例如 Member(Container&lt;0x00000FFF&gt; full):f(full.f){} 在浮动成员的情况下。我将如何在复合类型中调用 baseconstructor?
  • 或者为了避免复合类型还没有定义的问题,我们把固定的struct Full{float f, int i, bool b};作为参数
  • 好的 - 这就像一个魅力!我现在可以用一行创建任意 POD 之类的对象 - 非常感谢 - 我很佩服你的模板技巧! =)
【解决方案3】:

如果您正在做我认为的事情,请查看 boost::variant,这正是您想要做的事情。

【讨论】:

  • 谢谢,但我想 boost::variant 对我来说不是正确的解决方案 - 我希望生成的类型具有可确定的内存布局和最小的大小
【解决方案4】:

好的,我使用你的 Container 结构,将它们与 XCont 结合,然后定义你想要的 XContainer:

// a (bit-)LIST is an int that contains the value (TAIL<<1|HEAD),
// where TAIL is a LIST, and HEAD is either 1 or 0.
// while iterating through the LIST from right,
// SHL counts how far each consumed HEAD has to be shifted left,
// back to its original position.

template<int TAIL,int HEAD,int SHL>
struct XCont;

//the empty LIST
template<int SHL>
struct XCont<0,0,SHL>{};

//HEAD equals 0, so we just recurse through the TAIL.
template<int TAIL,int SHL>
struct XCont<TAIL,0,SHL>:public XCont< (TAIL>>1) , (TAIL&1) , (SHL+1) >{};

//HEAD equals 1, so we do like above, but we have to append Container< (1<<SHL) >.
template<int TAIL,int SHL>
struct XCont<TAIL,1,SHL>:public XCont< (TAIL>>1) , (TAIL&1) , (SHL+1) >,public Container< (1<<SHL) >{};


template<int E>
struct XContainer:public XCont< (E>>1) , (E&1) , (0) >{};

它是这样工作的:

  • 位掩码将被解释为从右到左的位 LIST(最低有效位在前。
  • 我们通过对 LIST 进行位移 (>>) 来遍历位。
  • LIST 以函数式编程风格表示为 HEAD 和 TAIL 的元组,其中 HEAD 是第一个元素,TAIL 是没有该 HEAD 的其余元素的 LIST。
  • 每当我们发现一个 1 位作为 HEAD 时,我们都想重新计算它的位位置,因此我们通过 SHL 对其进行计数。当然,还有其他方法,例如在列表上移动掩码并在 0 和非 0 上测试其值。

这些确实相等:

  • XContainer
  • XContainer
  • XContainer
  • XCont>1) , (0x07&1) , (0) >
  • XCont>1) , (EINT) , (0) >
  • XCont>1) , (EINT) , (0) >
  • XCont>1) , (EINT>>0) , (0) >
  • XCont,其中...
    • TAIL = ((EBOOL | EFLOAT) >>1)
    • 1 = (EINT>>0)
    • SHL = (0)
    • EINT = (1
  • XCont>1,TAIL&1,SHL+1 > ++ 容器
  • ...
  • XCont ++ 容器 ++ 容器 ++ 容器
  • XCont ++ 容器 ++ 容器 ++ 容器

C++ 模板的行为类似于 Haskell 中的模式匹配。 所以,对我来说,在没有任何花哨的 Haskell 东西的情况下,用简单的 Haskell 函数风格来考虑它会更容易。如果有人好奇:

xcontainer :: Int -> String
xcontainer(e) = "struct XContainer:" ++ (
                   xcont( (e .>>. 1) , (e .&. 1) , (0) )
                ) ++ "{}"

xcont :: (Int,Int,Int) -> String
xcont(   0,0,shl) = "public XCont<0,0," ++ show(shl) ++ ">"
xcont(tail,0,shl) = (  xcont( (tail .>>. 1) , (tail .&. 1) , (shl+1) )
                    )
xcont(tail,1,shl) = (  xcont( (tail .>>. 1) , (tail .&. 1) , (shl+1) )
                    ) ++ "," ++ container(1 .<<. shl)

container :: Int -> String
container(e) = "public Container<" ++ show(e) ++ ">"

(这是有效的 Haskell,但采用非 Haskell 书写风格。)

【讨论】:

    猜你喜欢
    • 2011-01-27
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-08
    • 1970-01-01
    • 2012-01-17
    • 2021-01-21
    相关资源
    最近更新 更多