Reputation: 485
I'm looking to create a generic vertex data container with strong typing using templates. A partial interface would look like this:
template <VertexFormat VF>
class VertexData
{
public:
template<uint32_t I>
(StronglyTypedVertex*) vertices();
};
where VertexFormat is an enum, I is an index for different data streams, and StronglyTypedVertex is the resulting vertex data. Given vertex data stored as two separate streams of positions and texture coordinates (enum VertexFormat::Pos3_TexCoord2
), using the above vertex data container would look like this:
VertexData<VertexFormat::Pos3_TexCoord2> vertexData;
Vector3* positions = vertexData.vertices<0>();
Vector2* texCoords = vertexData.vertices<1>();
This seems like the kind of thing that type traits would work for. I've managed to get something working using a flat type trait with 2 properties, like so:
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;
};
and then, the signature of VertexData::vertices
becomes:
template<uint32_t I>
VertexTraits<VF, I>::Type* vertices();
However, this isn't as convenient as I'd like because each permutation of vertex format and stream index requires its own type trait specialization. I was hoping to be able to do a single vertex trait with all streams in it, like so:
template<>
struct VertexTraits<VertexFormat::Pos3_TexCoords2>
{
using Stream0Type = Vector2; // Or some other similar declaration
using Stream1Type = Vector3;
};
I've tried nesting trait types with a Stream trait inside the VertexTrait, and I've tried using inheritance through a CRTP, but I haven't been able to get the syntax quite right for either case. What approach would work for this? Can it be done in a way that would introduce a static assert or compile-time error if a stream is used that wasn't defined (i.e.: Stream2Type in the above example)?
Upvotes: 0
Views: 835
Reputation: 26362
You can nest traits like this:
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;
};
However, the syntax for using it is quite ugly:
template<uint32_t I>
typename VertexTraits<VF>::template Type<I>* vertices();
You can use a type alias
template<VertexFormat VF, uint32_t I>
using VertexTraits_ = typename VertexTraits<VF>::template Type<I>;
and then write
template<uint32_t I>
VertexTraits_<VF, I>* vertices();
Do not forget to use typename
and template
for dependent types.
To avoid ugly looking specializations of TypeSelector
outside VertexTraits
, one can use decltype
and overload resolution:
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>{}));
};
Upvotes: 3