alcoforado
alcoforado

Reputation: 556

How to have different implementation of a template class function based on the type of a specific member of the parameterized type

So Here is the simplified problem, suppose We have two types

struct Shape2D
{
    Vec2 Position;
};

struct Shape3D
{
    Vec3 Position;
};

I want to create a template class test

template<class T>
class Test
{
  public:
       int method1()
       {
          //return 1 if member T::Position is Vec2,
          //return 2 if member T::Position is Vec3
       }
}

such that the following code works

Test<A> ta;
Test<B> tb; 
assert(ta.method1() == 1);
assert(tb.method1() == 2);

The real context of this problem is for an OpenGL Engine. I want to be able to use the same serialization class for 2D and 3D Vertices Data without having to write a 3D and 2D version.

Upvotes: 1

Views: 5283

Answers (3)

skypjack
skypjack

Reputation: 50568

You can use a couple of function declarations (definitions are not required in this case), std::declval and std::integral_constant to solve it at compile-time.
It follows a minimal, working example:

#include<type_traits>
#include<utility>

struct Vec2 {};
struct Vec3{};

struct Shape2D { Vec2 Position; };
struct Shape3D { Vec3 Position; };

template<class T>
class Test {
    static constexpr std::integral_constant<int, 1> method1(Vec2);
    static constexpr std::integral_constant<int, 2> method1(Vec3);

public:
    constexpr int method1() {
        return decltype(method1(std::declval<T>().Position))::value;
    }
};

int main() {
    Test<Shape2D> ta;
    Test<Shape3D> tb; 
    static_assert(ta.method1() == 1, "!");
    static_assert(tb.method1() == 2, "!");
}

The solution above fails to compile ifT hasn't a data member named Position the type of which is either Vec2 or Vec3.


Another possible approach that requires a default value instead could be this:

constexpr int method1() {
    return
        (std::is_same<decltype(std::declval<T>().Position), Vec2>::value
        ? 1 : (std::is_same<decltype(std::declval<T>().Position), Vec3>::value
        ? 2 : 0));
}

That is the ternary operator used in conjunction with std::is_same, nothing more.


If you can use C++17, you can also base your solution on if/else constexpr:

constexpr int method1() {
    if constexpr(std::is_same_v<decltype(std::declval<T>().Position), Vec2>) {
        return 1;
    } else if constexpr(std::is_same_v<decltype(std::declval<T>().Position), Vec3>) {
        return 2;
    }
}

Upvotes: 1

Miles Budnek
Miles Budnek

Reputation: 30694

There are a few ways to approach this.

The simplest would be to just use normal overload resolution:

template<class T>
class Test
{
private:
    T myT;

    int internal(Vec2)
    {
        return 1;
    }

    int internal(Vec3)
    {
        return 2;
    }
public:
    Test() : myT{} {}

    int method1()
    {
        return internal(myT.Position);
    }
};

This requires that you actually have an instance of T. If you don't, then you'll need to use a template based approach. That's a fairly deep topic, but one approach that does what you want in your example is this:

template <typename T>
int internal();

template <>
int internal<Vec2>()
{
    return 1;
}

template <>
int internal<Vec3>()
{
    return 2;
}

template<class T>
class Test
{
public:
    int method1()
    {
        return internal<decltype(T::Position)>();
    }
};

Upvotes: 2

Hai Pham Le
Hai Pham Le

Reputation: 144

You can have some oveload function to run different code, say you have int func (shape 2D x) and int func (shape3D x), so int your method1 you just call func (T.position) and compiler will help you to resolve the call.

Upvotes: 0

Related Questions