【问题标题】:Nested or inherited type traits嵌套或继承的类型特征
【发布时间】:2018-07-18 02:07:05
【问题描述】:

我正在寻找使用模板创建具有强类型的通用顶点数据容器。部分界面如下所示:

template <VertexFormat VF>
class VertexData
{
public:
    template<uint32_t I>
    (StronglyTypedVertex*) vertices();
};

其中 VertexFormat 是一个枚举,I 是不同数据流的索引,StronglyTypedVertex 是结果顶点数据。给定顶点数据存储为两个独立的位置流和纹理坐标(枚举VertexFormat::Pos3_TexCoord2),使用上面的顶点数据容器将如下所示:

VertexData<VertexFormat::Pos3_TexCoord2> vertexData;
Vector3* positions = vertexData.vertices<0>();
Vector2* texCoords = vertexData.vertices<1>();

这似乎是类型特征可以解决的问题。我已经设法使用具有 2 个属性的平面类型特征来实现某些工作,如下所示:

template<VertexFormat VF, uint32_t I>
struct VertexTraits
{
};

template<>
struct VertexTraits<VertexFormat::Pos3_TexCoords2, 0>
{
    using Type = Vector3;
};

template<>
struct VertexTraits<VertexFormat::Pos3_TexCoords2, 1>
{
    using Type = Vector2;
};

然后,VertexData::vertices 的签名变为:

template<uint32_t I>
VertexTraits<VF, I>::Type* vertices();

但是,这并不像我想的那么方便,因为顶点格式和流索引的每个排列都需要自己的类型特征特化。我希望能够在其中包含所有流的单个顶点特征,如下所示:

template<>
struct VertexTraits<VertexFormat::Pos3_TexCoords2>
{
    using Stream0Type = Vector2; // Or some other similar declaration
    using Stream1Type = Vector3;
};

我尝试过在 VertexTrait 中使用 Stream 特征嵌套特征类型,并尝试通过 CRTP 使用继承,但对于这两种情况,我都无法获得完全正确的语法。什么方法可以解决这个问题?如果使用未定义的流(即:上例中的 Stream2Type),是否可以通过引入静态断言或编译时错误的方式完成?

【问题讨论】:

    标签: c++ templates c++14 typetraits


    【解决方案1】:

    您可以像这样嵌套特征:

    template<VertexFormat VF>
    struct VertexTraits;
    
    template<>
    struct VertexTraits<VertexFormat::Pos3_TexCoords2>
    {
    private:
        template<uint32_t I>
        struct TypeSelector;
    
    public:
        template<uint32_t I>
        using Type = typename TypeSelector<I>::Type;
    };
    
    template<>
    struct VertexTraits<VertexFormat::Pos3_TexCoords2>::TypeSelector<0>
    {
        using Type = Vector3;
    };
    
    template<>
    struct VertexTraits<VertexFormat::Pos3_TexCoords2>::TypeSelector<1>
    {
        using Type = Vector2;
    };
    

    但是,使用它的语法很丑:

    template<uint32_t I>
    typename VertexTraits<VF>::template Type<I>* vertices();
    

    你可以使用类型别名

    template<VertexFormat VF, uint32_t I>
    using VertexTraits_ = typename VertexTraits<VF>::template Type<I>;
    

    然后写

    template<uint32_t I>
    VertexTraits_<VF, I>* vertices();
    

    https://godbolt.org/g/s82sPN

    Do not forgettypenametemplate 用于依赖类型。

    为了避免TypeSelectorVertexTraits 之外看起来难看的特化,可以使用decltype 和重载解析:

    template<>
    struct VertexTraits<VertexFormat::Pos3_TexCoords2>
    {
        static Vector3 type_selector(std::integral_constant<uint32_t, 0>);
        static Vector2 type_selector(std::integral_constant<uint32_t, 1>);
    
        template<uint32_t I>
        using Type = decltype(type_selector(std::integral_constant<uint32_t, I>{}));
    };
    

    https://godbolt.org/g/VFRT9H

    【讨论】:

    • typenametemplate 关键字的要点很好,这是我在尝试中缺少的主要内容。不幸的是,由于类范围内的完整模板专业化 (godbolt.org/g/sSZaSA),无法在 Clang 上编译,尽管可以使用虚拟模板参数解决此问题。 MSVC 只是以error C2770: invalid explicit template argument(s) for 'VertexTraits&lt;VertexFormat::Pos3_TexCoords2&gt;::TypeSelector&lt;I&gt;::Type *VertexData&lt;VertexFormat::Pos3_TexCoords2&gt;::vertices(void)' 失败(无论是否使用类型别名)。
    • @Camille,我编辑了我的答案,请看一下。我不明白为什么 MSVC 无法编译第一个解决方案,可能是一个错误。 decltype 的解决方案已被 MSVC 接受,而且看起来更漂亮。
    • MSVC 对模板类型的完整性可能有点特别,这可能是问题的一部分。 decltype 方法绝对更优雅!但是尝试完成实现会遇到示例的盲点——如何定义和返回存储? vertices() 访问器需要专门化,而 VertexData 类保持非专门化,这是不允许的 (AFAIK) (godbolt.org/g/GdjLaQ)。解决这个问题需要专门化 VertexData,这违背了这种方法的目的......也许这不是一个可行的解决方案。 :)
    • 其实在我发这个的时候就想出了一个解决办法。并非所有东西都必须是模板……一个简单的开关盒就可以工作。 godbolt.org/g/6kVCLE有时只见树木不见森林!
    • 使用 C++14,您可以使用 auto 返回类型。您可以使用函数重载代替if constexpr:定义vertices_impl(std::integral_constant&lt;uint32_t, 0&gt;)vertices_impl(std::integral_constant&lt;uint32_t, 1&gt;),然后从vertices() 调用它:return vertices_impl(std::integral_constant&lt;uint32_t, I&gt;{})
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多