问题
So, I am trying to simplify the use of my generic classes and came across the following idea:
The following struct is given:
template <size_t size>
struct Vector {
std::array<float, size> data;
float& x = data[0];
float& y = data[1]; // Declare y only if size is > 0
float& z = data[2]; // Declare z only if size is > 1
float& w = data[3]; // Declare w only if size is > 2
};
Obviously, if I try to run the program like this, the array will throw an out of range exception.
Now is there a way to declare these aliases only if the condition (known at compile time) is given?
I thought of something in the manner of std::enable_if:
template <size_t size>
struct Vector {
std::array<float, size> data;
float& x = data[0];
declare_if<(size > 0), float&> y = data[1];
declare_if<(size > 1), float&> z = data[2];
declare_if<(size > 2), float&> w = data[3];
};
Also, I would prefer to not let the class derive from another, or fully specialize the class.
回答1:
AFAIK you can do that and keep your syntax only with inheritance or specialization.
If you want to avoid that you need to change a bit the interface. You need to make x
, y
, z
, t
methods. Actually method templates:
template <size_t size>
struct Vector {
std::array<int, size> data;
template <std::size_t S = size, class = std::enable_if_t<(S > 0)>>
auto x() -> int& { return data[0]; };
template <std::size_t S = size, class = std::enable_if_t<(S > 1)>>
auto y() -> int& { return data[1]; };
template <std::size_t S = size, class = std::enable_if_t<(S > 2)>>
auto z() -> int& { return data[2]; };
template <std::size_t S = size, class = std::enable_if_t<(S > 3)>>
auto t() -> int& { return data[3]; };
};
Vector<2> v;
v.x();
v.y();
v.z(); // error: no matching member function for call to 'z'
回答2:
The most probable is to resort to specialization:
template <size_t size>
class Vector {
std::array<float, size> data;
float& x = data[0];
float& y = data[1]; // Declare y only if size is > 1
float& z = data[2]; // Declare z only if size is > 2
float& w = data[3]; // Declare w only if size is > 3
};
template<> class Vector<0> {
std::array<float, 0> data;
};
template<> class Vector<1> {
std::array<float, 1> data;
float &x = data[0];
};
(Note that I've changed size limits in the comments to not exceed array's bounds.)
Additionally, if you don't like the idea of full specialization, well, you'll need to introduce those members in the generic case anyway, but their default bindings can change:
template <size_t size>
class Vector {
std::array<float, size> data;
float& x = data[0];
float& y = data[std::min(size - 1, 1)];
float& z = data[std::min(size - 1, 2)];
float& w = data[std::min(size - 1, 3)];
};
(If you're ok with x, y, z and w referring to the same array element.)
回答3:
I recommend approaching this a little bit differently than creating members that are references to other members (you have to manually implement the assignment operators for each one if you want your class to be assignable).
Why not some free functions that do the accessing for you?
template <std::size_t i, std::size_t size>
auto & get(Vector<size> & v) { return std::get<i>(v.data); }
template <std::size_t size>
auto & x(Vector<size> & v) { return get<0>(v.data); }
template <std::size_t size>
auto & y(Vector<size> & v) { return get<1>(v.data); }
//...
This will give a compile-time error if you try to access a member in a vector that has insufficient size
回答4:
If having functions returning reference instead of reference public member is an option, you can go with:
#include <array>
#include <type_traits>
template<size_t size>
struct Vector
{
std::array<float, size> data;
template<size_t s = size, std::enable_if_t<s >= 1, int> = 0> float& x() { return data[0]; }
template<size_t s = size, std::enable_if_t<s >= 2, int> = 0> float& y() { return data[1]; }
template<size_t s = size, std::enable_if_t<s >= 3, int> = 0> float& z() { return data[2]; }
template<size_t s = size, std::enable_if_t<s >= 4, int> = 0> float& w() { return data[3]; }
};
回答5:
I agree with the other answers suggesting use functions rather than references to access. A reason not mentioned so far is that with the references you cannot enforce constness on the data: a float&
in a const object is still modifiable even if its referencing a const member variable. Example derived from your code:
#include <iostream>
template <size_t size>
struct Vector {
Vector() { std::fill(data, data + 4, 0.0f); }
float data[4];
float& x = data[0];
float& y = data[1];
float& z = data[2];
float& w = data[3];
// read-write access
float& getx() { return data[0]; }
};
int main()
{
const Vector<4> v;
//++v.data[0]; // compile error
std::cout << v.x << std::endl;
++v.x; // modifies const through non-const ref!
std::cout << v.x << std::endl;
//++v.getx(); // compile error (member function enforces const)
}
回答6:
Why not declare a function template float& get() which you can std::enable_if based on size?
来源:https://stackoverflow.com/questions/48404182/create-member-alias-based-on-a-template-parameter-condition-c17