11

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?

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • 1
    Possible duplicate of [How to check for the type of a template parameter?](https://stackoverflow.com/questions/13636540/how-to-check-for-the-type-of-a-template-parameter) – frslm Oct 25 '17 at 18:33
  • You can turn `vec3` and `vec2` into templates – Justin Oct 25 '17 at 18:33
  • 2
    @frslm That post is about checking in the body of a function, which may be different from the definition of a class. Class definition is entirely compile-time dependent while function bodies can be evaluated at runtime. – François Andrieux Oct 25 '17 at 18:34
  • 1
    The easiest solution may be to use [`std::conditional`](http://en.cppreference.com/w/cpp/types/conditional). – François Andrieux Oct 25 '17 at 18:36
  • @Justin Hmmm, these vec2 and vec3 and so on are from a library, but I suppose I could wrap them in my own templated type. – Zebrafish Oct 25 '17 at 18:37
  • One option is to use type traits. This might be helpful: https://stackoverflow.com/questions/35070288/c-class-template-specialization-with-pointers/35070474#35070474. – R Sahu Oct 25 '17 at 18:37
  • 1
    You don't want to specialize because? – StoryTeller - Unslander Monica Oct 25 '17 at 18:38
  • @StoryTeller, Ummm, I wanted to use the "power" of C++ generics instead of rewriting all the methods for each type. If it's not possible I get it, although I'm looking into this std::conditional that was suggested. – Zebrafish Oct 25 '17 at 18:42
  • `std::conditional` could indeed work. You'd end up with something that looks like `std::conditional_t, vec3, std::conditional_t, dvec3, std::conditional_t, ivec3, void>>>` (of course with nicer formatting). So you could just wrap that inside an alias template and call it good (although it's not very readable IMO) – Justin Oct 25 '17 at 18:46
  • 1
    The most important point here: please use Eigen. It is a C++ linear algebra library that provides templated vectors (and matrices) and it was also designed for your use case. – Synxis Oct 25 '17 at 22:40
  • 1
    @Synxis I've heard of Eigen. I'm currently using GLM. Because I would like to start using the Bullet physics library I thought I may adopt their vectors and matrices, it's really annoying having 15 different types of the same thing and converting. For example if I want to use the Bullet library and I'm using another library for vector type, I'd have to convert to btVector3 or some such. – Zebrafish Oct 25 '17 at 22:49
  • 1
    I used to use GLM, but now I switched to Eigen and I find it better. More complete, and better designed I think. If you cannot change, then the given answer will do, but with eigen all of this is already done: `Eigen::Matrix` for a vector in `Scalar^N`. – Synxis Oct 25 '17 at 23:12
  • If you use GLM, the different vector types are all just typedefs to the same templated vector type. Pass your own template parameter through to them. See https://glm.g-truc.net/0.9.8/api/a00127.html – tkausl Oct 25 '17 at 23:29
  • @tkausi Yeah, I know their types are typedeffed too, but I'm trying to make a container called Vertex which is made up of one of the GLM typedeffed types. For example I might want to have an array of double type vertices for terrain, and float vertices for other objects.... Not sure, still thinking about it. – Zebrafish Oct 25 '17 at 23:36
  • @Zebrafish Since you seem to be using GLM, definitely just use their types. For your vec3: `tvec3

    ` does exactly what you want

    – Justin Oct 25 '17 at 23:48
  • 1
    Perhaps you like this better: http://coliru.stacked-crooked.com/a/8ba299fee117a4af – Johannes Schaub - litb Oct 25 '17 at 23:59
  • 1
    Alternative syntax using lambdas: http://coliru.stacked-crooked.com/a/e8310688ec528343 – Johannes Schaub - litb Oct 26 '17 at 00:09
  • @Zebrafish It's bad form to edit the solution into your question. People who want to know how you solved the problem will just look down to the accepted answer. (Or if you really think there is something lacking about all the answers, you can post your own answer and accept that.) Would you mind reverting your last edit? – David Z Oct 26 '17 at 01:00
  • @David Z The solution I used is quite different from the answer given by Justin in his answer. However I basically used his implementation that he gave in the comments. Being different from Justin's answer, should I still revert the edit, tell Justin to add it to his answer? Or give my own answer? – Zebrafish Oct 26 '17 at 02:53
  • 1
    @Zebrafish Definitely revert the edit, and then I would suggest posting your own answer because the solution you used is significantly different from what Justin suggested. For future reference, if an existing answer is enough to point a reader in the direction of what you did, then it's best to accept that answer, and you can _suggest_ that the poster add something to their post if you think it'd help. But the accepted answer doesn't have to show exactly what you did. It only has to explain how to solve the essential problem you're asking about, so future readers can also find the solution. – David Z Oct 26 '17 at 02:59
  • @David Right I'll do that. – Zebrafish Oct 26 '17 at 03:01
  • @Johannes Schaub So I was wrong when I thought that "if" statements aren't evaluated at compile-time. In your constexpr case it is. – Zebrafish Oct 26 '17 at 03:17

4 Answers4

21

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)

Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    @Zebrafish - This idea is behind everything you deemed the power of C++. – StoryTeller - Unslander Monica Oct 25 '17 at 18:59
  • @StoryTeller Do you think D or Rust will have any chance to overpass C++ as the more popular low level language with bells and whistles? I mean in a way when designing something many years later we have the experience to not repeat our mistakes or silly choices in design, in this way I would think they should have the potential to be "better", if done wisely of course. – Zebrafish Oct 25 '17 at 19:09
  • @Zebrafish There's always a chance, but I'm not counting on it. If they eventually do become more popular than C++, that will be quite far in the future. I personally think that companies will be more willing to spend money to improve C++ than to replace C++, since C++ already has a massive head start – Justin Oct 25 '17 at 19:11
  • @Zebrafish - IDK, I'm not familiar with either. Besides, historically the template mechanism's strength was a happy coincidence, not a conscious design choice. – StoryTeller - Unslander Monica Oct 25 '17 at 19:13
  • @Zebrafish - BTW, I gather from your first comment that this works for you. You should accept the answer then. It'd be good SO netiquette. – StoryTeller - Unslander Monica Oct 25 '17 at 19:20
  • @StoryTeller That's why I like to wait a bit before accepting answers, Johannes Schaub provided two different solutions in the comments. – Zebrafish Oct 26 '17 at 03:00
6

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;
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Why is the `Choose` needed? it seems to me like it *should* work without it (and without the ternaries) – Justin Oct 26 '17 at 17:24
  • 1
    @Justin lambdas are not constexpr (as far as I'm aware), so you can't directly put it as initializer. The chooser trick is explained here: http://www.artima.com/cppsource/foreach.html – Johannes Schaub - litb Oct 26 '17 at 20:14
  • Thought they were supposed to be constexpr in C++17 – Justin Oct 26 '17 at 20:15
  • @Justin Ah, you are right! Well, it didn't compile for me. Perhaps the compiler didn't support it yet or I made some mistake. So I guess you can actually remove all the Choose noise. – Johannes Schaub - litb Oct 26 '17 at 20:16
  • 1
    @Justin I retested it, and GCC / Clang accepts it without the Choose. Thanks for asking. I removed the confusing trick from the answer . – Johannes Schaub - litb Oct 26 '17 at 20:21
2

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;
};
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • You're completely right. Gee I feel silly. I didn't realise you can instantiate the templated classes themselves, as I've only used vec3, vec4, etc. They're typedeffed as something like highp_vec3, which is typedeffed as typedef tvec3. Thanks. – Zebrafish Oct 27 '17 at 00:11
0

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.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • If you're using `std::is_same_v`, that's C++17, so you can replace the `typename std::conditional<..>::type` with `std::conditional_t<...>` (that's C++14). I'd also recommend using `using` for type-aliases, but that can come to personal preference – Justin Oct 26 '17 at 04:32
  • @Justin nice. I tried to remove the std:: also but it seems "using namespace" isn't allowed within a struct. – Zebrafish Oct 26 '17 at 05:02