Qub1
Qub1

Reputation: 1244

Using SFINAE on non static data members?

I'm new to SFINAE and I'm trying to write a simple Vector template. What I'm trying to achieve is to enable the z member variable based on the dimensions set for the Vector.

I have tried to achieve this effect using the following code:

template<unsigned int DIMENSIONS>
class Vector {
    // The x and y variables (same as z)

    template<typename = typename std::enable_if<DIMENSIONS >= 3>::type>
    /// <summary>
    /// The z coordinate.
    /// </summary>
    Float& z;

    Vector() : x(values[0]), y(values[1]) {
    }

    // Other member functions and variables

    std::vector<float> values;
};

template <>
Vector<3>::Vector() : x(values[0]), y(values[1]), z(values[2]) {
}

This should enable the z variable when there are 3 or more dimensions. This makes the compiler complain with the following error:

'Vector<DIMENSIONS>::z': only static data member templates are allowed

Also, I'm not entirely sure how to use initialization lists with SFINAE in such a situation. Because if the dimensions are smaller than 3, z would not have to be initialized (since it wouldn't exist). So far I've used a specialized constructor for 3D vectors (see the code above).

But intelliisense still reports that Members 'x', 'y', 'z' are not initialized in this constructor.

Any help would be appreciated.

Upvotes: 2

Views: 564

Answers (5)

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

You could use inheritance.

template<int n>
struct VecBase;

template<>
struct VecBase<1> {
    float x;
};

template<>
struct VecBase<2> : VecBase<1> {
    float y;
};

template<>
struct VecBase<3> : VecBase<2> {
    float z;
};

template<>
struct VecBase<4> : VecBase<3> {
    float w;
};

And then define your vector class:

template<int n>
struct Vector : VecBase<n> {
    // operations
};

If you want to make n-dimentional operation, you can use std::get and std::index_sequence. Let's start by making overloads for std::get:

namespace std {

template<size_t I, int n, enable_if_t<(I == 0 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
    return vec.x;
}

template<size_t I, int n, enable_if_t<(I == 1 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
    return vec.y;
}

template<size_t I, int n, enable_if_t<(I == 2 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
    return vec.z;
}

template<size_t I, int n, enable_if_t<(I == 3 && I < n), int> = 0>
float& get(VecBase<n>& vec) {
    return vec.w;
}

}

Note that you will have to implement them for const.

Then, you can implement your operations by making a base class that implement operations:

template<int, typename>
struct VecOps;

template<int n, std::size_t... S>
struct VecOps<n, std::index_sequence<S...>> : VecBase<n> {
    float dotp(Vector<n>& vec) const {
        // Where sum is adding each parameter variadically.
        return sum((std::get<S>(*this) * std::get<S>(vec))...);
    }
};

And finally, make Vector extending VecOps:

template<int n>
struct Vector : VecOps<n, std::make_index_sequence<n>> {};

Note that I don't have a compiler at my disposition for the moment. If your got compiler error, just leave a comment and I'll check this out.

Upvotes: 1

user2296177
user2296177

Reputation: 2837

You could do something like this:

struct no_z_var {};

struct z_var
{
    float z;
};

template<std::size_t n>
struct my_vector : std::conditional_t<( n >= 3 ), z_var, no_z_var>
{
    float x, y;
};

int main()
{
    my_vector<3> mv3;
    mv3.z = 3.4f;
    my_vector<2> mv2;
    mv2.z = 3.4f; // error; no 'z'
}

However, is this a good solution/design? I'm not so sure; it can get messy. It will most likely present other difficulties during the implementation...

Upvotes: 6

Barry
Barry

Reputation: 303357

You simply can't conditionally enable variables like that. Your best bet is just to provide multiple specializations of Vector:

template <>
struct Vector<2> {
    float x, y;

    Vector(std::vector<float> const& values)
    : x(values[0])
    , y(values[1])
    { }
};

template <>
struct Vector<3> {
    float x, y, z;

    Vector(std::vector<float> const& values)
    : x(values[0])
    , y(values[1])
    , z(values[2])
    { }
};

// etc.

Though it might be more straightforward to use an array instead:

template <size_t DIM>
struct Vector {
    std::array<float, DIM> values;

    Vector(std::vector<float> const& vs)
    : Vector(vs, std::make_index_sequence<DIM>{})
    { }

private:
    template <size_t... Is>
    Vector(std::vector<float> const& vs, std::index_sequence<Is...> )
    : values{{vs[Is]...}}
    { }
};

Upvotes: 2

user3684240
user3684240

Reputation: 1590

You don't really need SFINAE here. Just use specialization:

template<unsigned int DIMENSIONS>
class Vector {
  template<int I>
  struct ZContainer {};
  template<> struct ZContainer<3> {
    float z;
  };
  ZContainer<DIMENSIONS> possibleZ;
};

This has the advantage that you don't need additional templates. You can just add functions to the ZContainers for your class behavior.

Upvotes: 0

lorro
lorro

Reputation: 10880

Specialize the entrie class for 1, 2, 3 dimensions if you want this, i.e., template<> class Vector<3> { ... }.

Upvotes: 0

Related Questions