Is there any way in C++17 or older to use single template implementation of function compute in the code below?
template <typename T>
class C
{
public:
T x, y;
C() = default;
C(T x, T y) : x(x), y(y) {}
operator T() const { return x; }
C operator +(const C<T> &c) const { return C(x + c.x, y + c.y); }
C operator +(const T &t) const { return C(x + t, y); }
};
template <typename T>
void compute(C<T> *a, T *b, size_t size)
{
while (size--)
a[size] = a[size] + b[size];
}
template <typename T>
void compute(T *a, C<T> *b, size_t size)
{
while (size--)
a[size] = a[size] + b[size];
}
template <typename T>
void compute(C<T> *a, C<T> *b, size_t size)
{
while (size--)
a[size] = a[size] + b[size];
}
int main()
{
const size_t size = 4;
C<float> a[size];
float b[size];
compute(a, b, size);
compute(b, a, size);
compute(a, a, size);
}
Yes, if you are willing to tolerate a little boilerplate (I imagine you have many, many functions just like compute).
template <typename A, typename B>
struct is_cable_t : std::false_type {};
template <typename A>
struct is_cable_t<A, C<A>> : std::true_type {};
template <typename A>
struct is_cable_t<C<A>, A> : std::true_type {};
template <typename A>
struct is_cable_t<C<A>, C<A>> : std::true_type {};
template <typename A, typename B>
constexpr bool is_cable_v = is_cable_t<A,B>::value;
template <typename K, typename S,
typename R = typename std::enable_if<is_cable_v<K,S>>::type>
void compute(K *a, S *b, size_t size)
{
while (size--)
a[size] = a[size] + b[size];
}
Related
I'm trying to write a very simple monotonic allocator that uses a fixed total memory size. Here is my code:
#include <map>
#include <array>
template <typename T, size_t SZ>
class monotonic_allocator
{
public:
using value_type = T;
monotonic_allocator() noexcept {}
[[nodiscard]]
value_type* allocate(std::size_t n)
{
size_t start = 0;
for (const auto& [alloc_start, alloc_size] : alloc_list_) {
if ((alloc_start - start) <= n) {
alloc_list_[start] = n;
return mem_.data() + start;
}
start = alloc_start + alloc_size;
}
throw std::bad_alloc{};
}
void deallocate(value_type* p, std::size_t n) noexcept
{
alloc_list_.erase(static_cast<size_t>(p - mem_.data()));
}
template <typename T1, size_t SZ1, typename T2, size_t SZ2>
friend bool operator==(monotonic_allocator<T1, SZ1> const& x, monotonic_allocator<T2, SZ2> const& y) noexcept;
private:
std::array<value_type, SZ> mem_;
std::map<size_t, size_t> alloc_list_{};
};
template <typename T1, size_t SZ1, typename T2, size_t SZ2>
bool operator==(monotonic_allocator<T1, SZ1> const& x, monotonic_allocator<T2, SZ2> const& y) noexcept
{
return SZ1 == SZ2 && x.mem_.data() == y.mem_.data();
}
template <typename T1, size_t SZ1, typename T2, size_t SZ2>
bool operator!=(monotonic_allocator<T1, SZ1> const& x, monotonic_allocator<T2, SZ2> const& y) noexcept
{
return !(x == y);
}
int main()
{
std::vector<int, monotonic_allocator<int, 4096>> vec = {1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,6};
}
But I get a strange error which says:
error: no type named 'type' in 'std::__allocator_traits_base::__rebind<monotonic_allocator<int, 4096>, int>'
Any idea how I can solve this problem? BTW, this code may have other problems too.
According to cppreference, rebind (which is part of the Allocator requirements) is only optional if your allocator is a template of the form <typename T, [possibly other type arguments]>. But your template is of the form <typename T, size_t N>, so it doesn't match (the size_t argument is a non-type argument).
So you have to add the rebind implementation yourself, like in the example in this question: How are allocator in C++ implemented?
I am writing a matrix library for generic types using expression templates.
The basic matrix class is a template class Matrix <typename Scalar, int RowSize, int ColumnSize>
which inherits from MatrixXpr< Matrix<Scalar, RowSize, ColumnSize> >
where MatrixXpr is the parent class for the expression templates "MatrixSum", "MatrixProduct" etc.
For example:
template <typename Mat, typename Rix>
class MatrixProduct : public MatrixXpr< MatrixProduct<Mat,Rix> >
{
private:
const Mat& A_;
const Rix& B_;
public:
using value_type= std::common_type_t<typename Mat::value_type, typename Rix::value_type>;
MatrixProduct(const Mat& A, const Rix& B) : A_(A), B_(B) {}
value_type operator()(int i, int j) const {
value_type out{ 0 };
for (int k = 0; k < A_.Columns(); ++k) out += A_(i, k) * B_(k, j);
return out;
}
};
The * operator is then defined outside
template <typename Mat, typename Rix>
MatrixProduct<Mat, Rix> inline const operator*(const MatrixXpr<Mat>& A, const MatrixXpr<Rix>& B)
{
return MatrixProduct<Mat, Rix>(A, B);
}
Now I wish to implement also a Scalar*Matrix class. But I fail to define the correct value_type:
template <typename Scalar, typename Mat>
class ScalarMatrixProduct : public MatrixXpr< ScalarMatrixProduct<Scalar, Mat> >
{
private:
const Scalar& A_;
const Mat& B_;
public:
using value_type = std::common_type_t<typename Mat::value_type, typename Scalar>;
ScalarMatrixProduct(const Scalar& A, const Mat& B) : A_(A), B_(B) {}
value_type operator()(int i, int j) const {
return A_ * B_(i, j);
}
};
template <typename Scalar, typename Mat>
typename std::enable_if < (!is_matrix<Scalar>::value),
ScalarMatrixProduct<Scalar, Mat > >::type const operator*(const Scalar& A, const MatrixXpr<Mat>& B)
{
return ScalarMatrixProduct<Scalar, Mat>(A, B);
}
On Mac and Linux I get an compilation error of this sort:
template argument 2 is invalid 102 | using value_type =
std::common_type_t<typename Mat::value_type, typename Scalar>;
Interestingly, it compiles on Windows.
Any hints for what's wrong would be helpful.
Thanks in advance.
Complete example:
#include <type_traits>
#include <iostream>
#include <array>
#include <initializer_list>
///////////Expression Template Base Class for CRTP
template <class MatrixClass> struct MatrixXpr {
decltype(auto) operator()(int i, int j) const {
return static_cast<MatrixClass const&>(*this)(i, j);
}
operator MatrixClass& () {
return static_cast<MatrixClass&>(*this);
}
operator const MatrixClass& () const {
return static_cast<const MatrixClass&>(*this);
}
int Rows()
{
return static_cast<MatrixClass&>(*this).Rows();
}
int Columns()
{
return static_cast<MatrixClass&>(*this).Columns();
}
int Rows() const
{
return static_cast<const MatrixClass&>(*this).Rows();
}
int Columns() const
{
return static_cast<const MatrixClass&>(*this).Columns();
}
friend int Rows(const MatrixXpr& A)
{
return A.Rows();
}
friend int Columns(const MatrixXpr& A)
{
return A.Columns();
}
};
template <typename MatrixClass>
std::ostream& operator<<(std::ostream& os, const MatrixXpr<MatrixClass>& A)
{
for (int r = 0; r < Rows(A); ++r) {
os << '[';
for (int c = 0; c < Columns(A); ++c)
os << A(r, c) << (c + 1 < Columns(A) ? " " : "");
os << "]\n";
}
return os;
}
/////////// Matrix Product
template <typename Mat, typename Rix>
class MatrixProduct : public MatrixXpr< MatrixProduct<Mat, Rix> >
{
private:
const Mat& A_;
const Rix& B_;
public:
using value_type = std::common_type_t<typename Mat::value_type, typename Rix::value_type>;
MatrixProduct(const Mat& A, const Rix& B) : A_(A), B_(B)
{
std::cout << "MatrixMatrixProduct Constructor\n";
}
int Rows() const { return A_.Rows(); }
int Columns() const { return B_.Columns(); }
value_type operator()(int i, int j) const {
value_type out{ 0 };
for (int k = 0; k < A_.Columns(); ++k) out += A_(i, k) * B_(k, j);
return out;
}
};
/////////// Scalar Matrix Product
template <typename Scalar, typename Mat>
class ScalarMatrixProduct : public MatrixXpr< ScalarMatrixProduct<Scalar, Mat> >
{
private:
const Scalar& A_;
const Mat& B_;
public:
using value_type = std::common_type_t<typename Mat::value_type, typename Scalar>;
ScalarMatrixProduct(const Scalar& A, const Mat& B) : A_(A), B_(B) {
std::cout << "ScalarMatrixProduct Constructor\n";
}
int Rows() const { return B_.Rows(); }
int Columns() const { return B_.Columns(); }
value_type operator()(int i, int j) const {
return A_ * B_(i, j);
}
};
//The following two functions are Helpers for initializing an array.
//Source: https://stackoverflow.com/a/38934685/6176345
template<typename T, std::size_t N, std::size_t ...Ns>
std::array<T, N> make_array_impl(
std::initializer_list<T> list,
std::index_sequence<Ns...>)
{
return std::array<T, N>{ *(list.begin() + Ns) ... };
}
template<typename T, std::size_t N>
std::array<T, N> make_array(std::initializer_list<T> list) {
if (N > list.size())
throw std::out_of_range("Initializer list too small.");
return make_array_impl<T, N>(list, std::make_index_sequence<N>());
}
/////////// Matrix class
template <typename Scalar, int RowSize, int ColumnSize = RowSize>
class Matrix : public MatrixXpr< Matrix<Scalar, RowSize, ColumnSize> >
{
std::array<Scalar, RowSize* ColumnSize> data_;
public:
using value_type = Scalar;
const static int rows_ = RowSize;
const static int columns_ = ColumnSize;
int Rows() const { return rows_; }
int Columns() const { return columns_; }
Matrix() : data_{ Scalar(0) } {};
Matrix(const Matrix& other) = default;
Matrix(Matrix&& other) = default;
Matrix& operator=(const Matrix& other) = default;
Matrix& operator=(Matrix&& other) = default;
~Matrix() = default;
Matrix(std::initializer_list<Scalar> data) : data_(make_array<Scalar, RowSize* ColumnSize>(data)) {}
template <typename Source>
Matrix& operator=(const MatrixXpr<Source>& source)
{
for (int i = 0; i < rows_; ++i)
for (int j = 0; j < columns_; ++j)
data_[MatrixIndex(i, j)] = source(i, j);
return *this;
}
template <typename Source>
Matrix(const MatrixXpr<Source>& source)
{
for (int i = 0; i < rows_; ++i)
for (int j = 0; j < columns_; ++j)
data_[MatrixIndex(i, j)] = source(i, j);
}
Scalar& operator()(int i, int j) {
return data_[MatrixIndex(i, j)];
}
const Scalar& operator()(int i, int j) const {
return data_[MatrixIndex(i, j)];
}
private:
inline static int MatrixIndex(int i, int j)
{
return i * columns_ + j;
}
};
/////////// Multiplication operators
template <typename Mat, typename Rix>
MatrixProduct<Mat, Rix> inline const operator*(const MatrixXpr<Mat>& A, const MatrixXpr<Rix>& B)
{
std::cout << "Matrix Matrix Multiplication\n";
return MatrixProduct<Mat, Rix>(A, B);
}
template <typename Scalar, typename Mat>
typename std::enable_if_t<!std::is_base_of_v<MatrixXpr<Scalar>, Scalar>,
ScalarMatrixProduct<Scalar, Mat >> const operator*(const Scalar& A, const MatrixXpr<Mat>& B)
{
return ScalarMatrixProduct<Scalar, Mat>(A, B);
}
/////////// Failing example
int main()
{
Matrix<int, 2, 2> m = { 1,0,0,1 };
auto n = 3 * m;
std::cout << n;
std::cout << m * n;
//std::cout << n * m; // Error
return 0;
}
Edit:
The above code originally had two problems.
The first one is that my type checking failed to see which overload of the *operator was being used. The above implementation with std::is_base_of_v<MatrixXpr<Scalar>, Scalar> fixed it and is is working correctly.
I do not know why this old code did not work. Here is the old version:
template <typename T>
struct is_matrix : std::false_type {};
template <typename T>
struct is_matrix<const T> : is_matrix<T> {};
template <typename MatrixClass>
struct is_matrix<MatrixXpr<MatrixClass> > : std::true_type {};
template <typename Scalar, typename Mat>
typename std::enable_if < (!is_matrix<Scalar>::value),
ScalarMatrixProduct<Scalar, Mat > >::type const operator*(const Scalar& A, const MatrixXpr<Mat>& B)
{
std::cout << "Scalar Matrix Multiplication\n";
return ScalarMatrixProduct<Scalar, Mat>(A, B);
}
I want to design a Matrix class which allows to specify the method of memory management, i.e.
Matrix<Allocator::vector, int> mv(2, 2);
Matrix<Allocator::unique_pointer, int> mu(2, 2);
mv(1, 1) = 1;
mu(1, 2) = 1;
and with mv and mu being compatible to each other (i.e. when I overload the "+" operator) despite different memory strategies.
I found already very good help from Class template specializations with shared functionality which does the same for an n dimensional vector class.
using dim_t = std::pair<size_t, size_t>;
enum class Allocator { vector, raw_pointer, unique_pointer };
template <typename T>
class MatrixBase {
public:
MatrixBase() : MatrixBase(0, 0){};
MatrixBase(size_t m, size_t n) : dim_(m, n){};
size_t rows() const;
virtual T& at(size_t i, size_t j);
private:
dim_t dim_;
};
template <typename T>
size_t MatrixBase<T>::rows() const {
return dim().first;
}
template <Allocator A, typename T>
class Matrix : public MatrixBase<T> {};
template <typename T>
class Matrix<Allocator::vector, T> : public MatrixBase<T> {
private:
std::vector<T> data_;
};
template <typename T>
T& Matrix<Allocator::vector, T>::at(size_t i, size_t j) {
return data_[i * rows() + j];
}
template <typename T>
class Matrix<Allocator::unique_pointer, T> : public MatrixBase<T> {
private:
std::unique_ptr<T[]> data_;
};
template <typename T>
T& Matrix<Allocator::unique_pointer, T>::at(size_t i, size_t j) {
return data_[i * rows() + j];
}
Unfortunately the compiler complains
./matrix.hpp:100:34: error: out-of-line definition of 'at' does not match any declaration in 'Matrix<linalg::Allocator::vector,
type-parameter-0-0>'
T& Matrix<Allocator::vector, T>::at(size_t i, size_t j) {
^
./matrix.hpp:103:20: error: use of undeclared identifier 'rows'
return data_[i * rows() + j];
I assume the error originates from
template <typename T>
class Matrix<Allocator::vector, T> : public MatrixBase<T> {
private:
std::vector<T> data_;
};
How do I fix this?
I have fixed Your code. Here You go:
using dim_t = std::pair<size_t, size_t>;
enum class Allocator { vector, raw_pointer, unique_pointer };
template <typename T>
class MatrixBase {
public:
MatrixBase() : MatrixBase(0, 0){};
MatrixBase(size_t m, size_t n) : dim_(m, n){};
size_t rows() const;
virtual T& at(size_t i, size_t j);
private:
dim_t dim_;
};
template <typename T>
size_t MatrixBase<T>::rows() const {
return dim_.first;
}
template <Allocator A, typename T>
class Matrix : public MatrixBase<T> {};
template <typename T>
class Matrix<Allocator::vector, T> : public MatrixBase<T> {
public:
T &at(size_t i, size_t j);
private:
std::vector<T> data_;
};
template <typename T>
T& Matrix<Allocator::vector, T>::at(size_t i, size_t j) {
return data_[i * this->rows() + j];
}
template <typename T>
class Matrix<Allocator::unique_pointer, T> : public MatrixBase<T> {
public:
T &at(size_t i, size_t j);
private:
std::unique_ptr<T[]> data_;
};
template <typename T>
T& Matrix<Allocator::unique_pointer, T>::at(size_t i, size_t j) {
return data_[i * this->rows() + j];
}
First of all, if You override a method You have to declare it in derived class. Secondly, the rows() function could not be resolved by default as a member.
Move the definition inside will solve the problem. This problem is due to scoping.
template <typename T>
class Matrix<Allocator::vector, T> : public MatrixBase<T> {
public: // or private:
T& at(size_t i, size_t j) {
return data_[i * MatrixBase<T>::rows() + j];
}
private:
std::vector<T> data_;
};
Consider the following example
class P {
public:
virtual int test();
};
class C : public P {
};
int C::P:: test() { // if you omit P, it will not compile
return 21;
}
Or you can re-declare it inside C, so it will be in C's scope.
I try to make a generic, but still efficient multi dimension Point class.
What I have is a Dimensions enum
enum Dimension : std::size_t { _2D = 2, _3D = 3 };
And a Point class
template <typename T, Dimension D>
class Point : public std::array<T, D>
{
public:
T& at(size_t idx) { return std::array<T,D>::at(idx); };
const T& at(size_t idx) const { return std::array<T,D>::at(idx); };
Dimension dim() const { return D; }
...
};
I would like to create nices constructors so I added (outside my class definition)
template <typename T>
Point<T,_2D>::Point(T x, T y) { at(0) = x; at(1) = y; }
template <typename T>
Point<T,_3D>::Point(T x, T y, T z) { at(0) = x; at(1) = y; at(2) = z; }
But still I cannot use thoses. The compiler tells me only default (empty) and copy constructors are registered.
Question:
How do I define constructors with arguments list length being dependant on my Dimension template ?
I would do it like this because I'm too lazy to write many versions of the same thing:
template<class T, Dimension D>
class Point : public std::array<T,D> {
template<class... Args>
Point(Args... vs) :
std::array<T,D>{{vs...}}
{
static_assert(sizeof...(Args) == D, "wrong number of args");
}
...
};
There are a few ways to accomplish this. In your case, it might be easiest to use std::enable_if:
template <typename T, Dimension D>
class Point : public std::array<T, D>
{
public:
template <
Dimension D2=D,
typename = typename std::enable_if<D2==_2D>::type
>
Point(T x,T y);
template <
Dimension D2=D,
typename = typename std::enable_if<D2==_3D>::type
>
Point(T x,T y,T z);
T& at(size_t idx) { return std::array<T,D>::at(idx); };
const T& at(size_t idx) const { return std::array<T,D>::at(idx); };
Dimension dim() const { return D; }
...
};
Another option would be to break this into multiple classes and use specialization:
template <typename T, Dimension D>
class PointBase : public std::array<T, D>
{
public:
T& at(size_t idx) { return std::array<T,D>::at(idx); };
const T& at(size_t idx) const { return std::array<T,D>::at(idx); };
Dimension dim() const { return D; }
...
};
template <typename T, Dimension D> class Point;
template <typename T>
class Point<T,_2D> : public PointBase<T,_2D> {
public:
Point(T x,T y);
};
template <typename T>
class Point<T,_3D> : public PointBase<T,_3D> {
public:
Point(T x,T y,T z);
};
I'm trying to implement a Vector class for use in my graphics projects. I'd like to template the vector by length and type, and I'd like to be able to use A.x, A.y, A.z, A.w to access the members of vector A (for example).
Here's my first attempt. I'm definitely not an expert in C++ templates! I was trying to implement a general Vector class with specialized versions for Vec2, Vec3, and Vec4. Each of the specialized classes would have a union allowing me to access the coordinates using their names. Unfortunately, I can't figure out how to do this without re-implementing every vector function for each of the specialized classes.
Keep in mind that I want to implement some functions that only apply to vectors of a certain length. For example, cross product only applies to vec3, but dot product (or operator*) applies to vectors of all length.
#include <cstdint>
using namespace std;
//*********************************************************************
// Vector implementation parameterized by type and size.
//*********************************************************************
template <typename T, size_t SIZE>
class Vector {
public:
T data[SIZE];
size_t size;
Vector(T* arr);
};
template <typename T, size_t SIZE> Vector<T, SIZE>::Vector(T* arr) {
size = SIZE;
for(int i=0; i<size; i++) {
data[i] = arr[i];
}
}
//*********************************************************************
// Vec2 is a specialization of Vector with length 2.
//*********************************************************************
typedef Vector<float, 2> Vec2f;
typedef Vector<int, 2> Vec2d;
template <typename T>
class Vector <T, 2> {
public:
union {
T data[2];
struct {
T x;
T y;
};
};
size_t size;
Vector(T x, T y);
};
template <typename T> Vector<T, 2>::Vector(T x, T y) {
data[0] = x; data[1] = y;
size = 2;
}
//*********************************************************************
// Vec3 is a specialization of Vector with length 3.
//*********************************************************************
typedef Vector<float, 3> Vec3f;
typedef Vector<int, 3> Vec3d;
template <typename T>
class Vector <T, 3> {
public:
union {
T data[3];
struct {
T x;
T y;
T z;
};
};
size_t size;
Vector(T x, T y, T z);
};
template <typename T> Vector<T, 3>::Vector(T x, T y, T z) {
data[0] = x; data[1] = y; data[2] = z;
size = 3;
}
//*********************************************************************
// Vec4 is a specialization of Vector with length 4.
//*********************************************************************
typedef Vector<float, 4> Vec4f;
typedef Vector<int, 4> Vec4d;
template <typename T>
class Vector <T, 4> {
public:
union {
T data[4];
struct {
T x;
T y;
T z;
T w;
};
};
size_t size;
Vector(T x, T y, T z, T w);
};
template <typename T> Vector<T, 4>::Vector(T x, T y, T z, T w) {
data[0] = x; data[1] = y; data[2] = z; data[3] = w;
size = 4;
}
The usual workaround to avoid repeatedly implementing identical features in multiple specializations is to inherit from a common base class, and implement those features in the base:
template <typename T>
struct VectorBase {
// common stuff
};
template <typename T, std::size_t N>
struct Vector : VectorBase<T> {
// ...
};
template <typename T>
struct Vector<T, 2> : VectorBase<T> {
// ...
};
template <typename T>
struct Vector<T, 3> : VectorBase<T> {
// ...
friend Vector<T, 3> cross(const Vector<T, 3>&, const Vector<T, 3>&);
};
The next problem you will have is needing to access members in the derived class from the common base (e.g., get the value of x, or size()). You do that using the Curiously Recurring Template Pattern (CRTP):
template <typename T, typename CRTP>
struct VectorBase {
CRTP& crtp() { return static_cast<CRTP&>(*this); }
const CRTP& crtp() const { return static_cast<const CRTP&>(*this); }
std::size_t size() const {
return std::extent<decltype(CRTP::data)>::value;
}
void zero() {
std::fill(std::begin(crtp().data), std::end(crtp().data), T());
}
using iterator = T*;
using const_iterator = const T*;
iterator begin() { return &crtp().data[0]; }
iterator end() { return &crtp().data[0] + size(); }
const_iterator begin() const { return &crtp().data[0]; }
const_iterator end() const { return &crtp().data[0] + size(); }
T& operator [] (std::size_t i) {
return crtp().data[i];
}
const T& operator [] (std::size_t i) const {
return crtp().data[i];
}
};
template <typename T, std::size_t N>
struct Vector : VectorBase<T, Vector<T, N>> {
union {
T data[N];
struct {
T x, y, z, w;
};
};
};
template <typename T>
struct Vector<T, 2> : VectorBase<T, Vector<T, 2>> {
union {
T data[2];
struct {
T x, y;
};
};
};
template <typename T>
struct Vector<T, 3> : VectorBase<T, Vector<T, 3>> {
union {
T data[3];
struct {
T x, y, z;
};
};
};
template <typename T, typename U, std::size_t N>
auto operator * (const Vector<T, N>& a, const Vector<U, N>& b)
-> Vector<decltype(a[0] * b[0]), N> {
Vector<decltype(a[0] * b[0]), N> result;
for (std::size_t i = 0; i < N; ++i) {
result[i] = a[i] * b[i];
}
return result;
}
template <typename T, typename U, std::size_t N>
auto dot(const Vector<T, N>& a, const Vector<U, N>& b)
-> decltype(a[0] * b[0]) {
auto product = a * b;
using V = decltype(product.x);
return std::accumulate(std::begin(product), std::end(product), V(0));
}
** Sample Code at Coliru **
There two issues with undefined behavior to which the commenters refer are:
The anonymous structs (e.g., struct { T x, y, z; };) are an GNU extension, and will thus likely only work with GCC and compatible compilers (clang).
Reading from a union member other than the member last stored into is typically undefined behavior; this particular example is at least borderline given that every type involved is standard-layout and that all values read/written are of the same type. I'll let someone else do the exact language lawyering and simply state that the code will almost certainly perform as intended when using a recent compiler that supports the anonymous struct extension.
If either of those non-standard requirements bothers you, drop the structs and unions so that the array is the only data member. Then add functions for the symbolic names, e.g. T& x() { return data[0]; }, it's only slightly more cumbersome.