Zebrafish
Zebrafish

Reputation: 13886

How can I use C++ template argument to decide which type of member is in a class

I would like to create a Vertex class, and would like to genericise it by being able to create a 32 bit float and 64 bit double version, and maybe int version. I would like to do this:

template <typename P>
struct Vertex
{
    if (typeid(P) == typeid(float))
    {
         vec3 position;
         vec3 normal;
         vec2 texcoords;
    }
    else if (typeid(P) == typeid(double))
    {
         dvec3 position; // This is a double vector
         dvec3 normal;
         dvec2 texcoords;
    }
    else if (typeid(P) == typeid(int))
    {
         ivec3 position; // This is an integer vector
         ivec3 normal;
         ivec2 texcoords;
    }

};

I don't think if statements aren't evaluated at compile-time, so this is just an illustration of what I would like to do. Is there any way to do this? Or do I have to specialise each type, or just rewrite all the different versions?

Upvotes: 11

Views: 2409

Answers (4)

HolyBlackCat
HolyBlackCat

Reputation: 96013

OP has mentioned in comments that they're using GML.

GLM vectors are actually templates, so there is no need for intricate solutions:

template <typename P>
struct Vertex
{
     tvec3<P> position;
     tvec3<P> normal;
     tvec2<P> texcoords;
};

Upvotes: 2

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 506837

Here's an alternative

template<typename T>
struct Type { typedef T type; };

template<typename T>
inline constexpr Type<T> type{};

template <typename P>
struct Vertex
{
    static constexpr auto D3 = []{ 
        if constexpr(std::is_same_v<P,float>)
            return type<vec3>;
        else if constexpr(std::is_same_v<P,double>)
            return type<dvec3>;
        else if constexpr(std::is_same_v<P,int>)
            return type<ivec3>;
    }();

    static constexpr auto D2 = []{ 
        if constexpr(std::is_same_v<P,float>)
            return type<vec2>;
        else if constexpr(std::is_same_v<P,double>)
            return type<dvec2>;
        else if constexpr(std::is_same_v<P,int>)
            return type<ivec2>;
    }();

    typename decltype(D3)::type position;
    typename decltype(D3)::type normal;
    typename decltype(D2)::type texcoords;
};

With a little bit of more effort on the Type template, you can improve the code of the lambdas quite a bit (perhaps you've seen boost hana, which follows this idea aswell)

template<typename T>
struct Type {
   typedef T type;   
   friend constexpr bool operator==(Type, Type) {
       return true;
   }
};

template<typename T1, typename T2>
constexpr bool operator==(Type<T1>, Type<T2>) {
    return false;
}

template<typename T>
inline constexpr Type<T> type{};

Now it will not need std::is_same_v anymore

template <typename P>
struct Vertex
{
    static constexpr auto D3 = [](auto t) { 
        if constexpr(t == type<float>)
            return type<vec3>;
        else if constexpr(t == type<double>)
            return type<dvec3>;
        else if constexpr(t == type<int>)
            return type<ivec3>;
    }(type<P>);

    static constexpr auto D2 = [](auto t) { 
        if constexpr(t == type<float>)
            return type<vec2>;
        else if constexpr(t == type<double>)
            return type<dvec2>;
        else if constexpr(t == type<int>)
            return type<ivec2>;
    }(type<P>);

    typename decltype(D3)::type position;
    typename decltype(D3)::type normal;
    typename decltype(D2)::type texcoords;
};

The ugly decltype writing could be fixed by using auto aswell

template<auto &t>
using type_of = typename std::remove_reference_t<decltype(t)>::type;

So you can write

type_of<D3> position;
type_of<D3> normal;
type_of<D2> texcoords;

Upvotes: 6

Zebrafish
Zebrafish

Reputation: 13886

This is just an addition to Justin's solution. It's actually his anyway. The idea to use std::conditional was François Andrieux's idea given in the comments. Something along these lines:

template <typename P>
struct vertex
{
    using vec3_t = std::conditional_t <std::is_same_v<float, P>,
        /*IF FLOAT*/     vec3,
        /*OTHERWISE*/    std::conditional_t <is_same_v<double, P>,
        /*IF DOUBLE*/    dvec3,
        /*IF INT*/       ivec3>>;

    vec3_t position;
    vec3_t normal;
    //vec2_t texcoords; WILL HAVE TO TYPEDEF THIS AS WELL.
};

Johannes Schaub has given two different solutions in the comments based on constexpr and decltype also.

Upvotes: 0

Justin
Justin

Reputation: 25277

You may want to have some sort of vec3 and vec2 selector type. If there are already templated versions of vec3 and vec2, just use them. Otherwise, you can use template specialization:

template <typename T>
struct vec_selector {};

template <>
struct vec_selector<float> {
    using vec3_type = vec3;
    using vec2_type = vec2;
};

template <>
struct vec_selector<double> {
    using vec3_type = dvec3;
    using vec2_type = dvec2;
};

template <>
struct vec_selector<int> {
    using vec3_type = ivec3;
    using vec2_type = ivec2;
};

template <typename P>
using vec3_select_t = typename vec_selector<P>::vec3_type;

template <typename P>
using vec2_select_t = typename vec_selector<P>::vec2_type;

Then you can simply write:

template <typename P>
struct Vertex
{
    vec3_select_t<P> position;
    vec3_select_t<P> normal;
    vec2_select_t<P> texcoords;
};

You could also just specialize the Vertex template, but it seems likely that it would be useful to have vec3_select_t elsewhere, and you'd have to repeat any member functions on Vertex (or else make the code more complicated)

Upvotes: 21

Related Questions