如何创建具有命名 public 成员(即 x、y、z、...)的可变大小 C++ 数组?

How to create variable sized C++ array with named public members i.e. x, y, z, ...?

我正在寻找一种方法来创建一个通用的类标准 std::array< T, N >,它具有命名的成员元素,即 x、y、z、...

有没有办法使用可变模板来构造这样一个对象,以便通过模板参数有条件地定义成员?

通过 operator[] 对对象进行类似数组的访问是绝对重要的,而且它的内存占用不会因额外的引用或指针而膨胀。

我考虑过模仿 TR1 的特征,但我真的不知道如何开始。

假设您要存储 int 类型

using mytype = int;
struct mytuple_ {
  mytype x;
  mytype y;
  mytype z;
} tup;

这符合您不添加任何额外内存使用的标准,并且只是在内存中布置数据。

假设设x=5

tup.x = 5;

没有什么好实现的:

#include <cstddef>

template <std::size_t Extent>
struct Tuple;

// You cant not use a template for declaring member names.
// Hence a set of pre-declared tuples.

template <> struct Tuple<1> {
    double x;
    double operator [] (std::size_t idx) const { return x; }
};

template <> struct Tuple<2> {
    double x;
    double y;
    double operator [] (std::size_t idx) const { return (idx < 1) ? x : y; }
};

template <> struct Tuple<3> {
    double x;
    double y;
    double z;
    double operator [] (std::size_t idx) const {
        return (idx < 1)
            ? x
            : (idx < 2)
                ? y
                : z;
    }
};

// However, you pay the price of conditional evaluation for accessing
// the members via operator [].

// An alternative is accessing the values through a std::array and member functions:

template <> struct Tuple<1> {
    std::array<double, 1> data;
    double x() const { return data[0]; }
    double operator [] (std::size_t) const { return data[0]; }
};

template <> struct Tuple<2> {
    std::array<double, 2> data;
    double x() const { return data[0]; }
    double y() const { return data[1]; }
    double operator [] (std::size_t idx) const { return data[idx]; }
};

template <> struct Tuple<3> {
    std::array<double, 3> data;
    double x() const { return data[0]; }
    double y() const { return data[1]; }
    double z() const { return data[2]; }
    double operator [] (std::size_t idx) const { return data[idx]; }
};

// However, now you have to write tuple.x(), which is annoying
// in mathematical equations.

为了完整起见:另一种选择是通过联合进行类型双关。但这是向未定义行为的阴暗面移动(参见:Opinions on type-punning in C++?)。

玩了一段时间后,我将回答我自己的问题。我想我已经找到了我要找的东西,尽管这只是一个粗略的草图。我会post这个,以防其他人对类似问题感到好奇。

定义: Tuple.hpp

#include <cassert>
#include <iostream>

namespace details
{

    template<bool>
    struct rule_is_greater_than_4;

    template<>
    struct rule_is_greater_than_4<true> {};

    template<>
    struct rule_is_greater_than_4<false> {};

    template<class T, size_t N, size_t M>
    class inner_storage : rule_is_greater_than_4< ( M > 4 )>
    {

    public:

        T x, y, z, w;

    private:

        T more_data[ N - 4 ];

    };

    template<class T, size_t N>
    class inner_storage<T, 2, N>
    {

    public:

        T x, y;

    };

    template<class T, size_t N>
    class inner_storage<T, 3, N>
    {

    public:

        T x, y, z;
    };

    template<class T, size_t N>
    class inner_storage<T, 4, N>
    {

    public:

        T x, y, z, w;

    };

}

template<class T, size_t N>
class Tuple : public details::inner_storage<T, N, N>
{

public:

    static_assert( N > 1, "Size of 'n-tuple' must be > 1." );



    // -- Constructors --

    Tuple();

    Tuple( T k );

    Tuple( T x, T y );

    Tuple( T x, T y, T z );

    Tuple( T x, T y, T z, T w );

    // -- Access operators --

    const size_t size();

    T &operator[]( const size_t i );
    T const &operator[]( const size_t i ) const;

    // -- Unary arithmetic operators --

    Tuple<T, N> &operator=( Tuple<T, N> const &t );

    friend std::ostream &operator<<( std::ostream &os, Tuple<T, N> &t );

};

// -- Unary operators --

template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t );

template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t );

// -- Binary arithmetic operators --

template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );

template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );

template<class T, size_t N>
Tuple<T, N> operator*( T const &s, Tuple<T, N> const &t2 );

template<class T, size_t N>
Tuple<T, N> operator*( Tuple<T, N> const &t1, T const &s );

template<class T, size_t N>
Tuple<T, N> operator/( Tuple<T, N> &t1, T const &s );

// -- Boolean operators --

template<class T, size_t N>
bool operator==( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );

template<class T, size_t N>
bool operator!=( Tuple<T, N> const &t1, Tuple<T, N> const &t2 );

// -- Stream operator --

template<class T, size_t N>
inline std::ostream &operator<<( std::ostream &os, Tuple<T, N> const &t );

#include "Tuple.inl"

执行:Tuple.inl

// -- Constructors --

template <class T, size_t N>
Tuple<T, N>::Tuple()
{

}

template <class T, size_t N>
Tuple<T, N>::Tuple( T k )
{

    for( size_t i = 0; i < N; i++ )
    {

        operator[]( i ) = k;

    }

}

template <class T, size_t N>
Tuple<T, N>::Tuple( T x, T y )
{

    static_assert( N == 2, "This constructor is resererved for 2-tuples." );

    this->x = x;
    this->y = y;

}

template <class T, size_t N>
Tuple<T, N>::Tuple( T x, T y, T z )
{

    static_assert( N == 3, "This constructor is resererved for 3-tuples." );

    this->x = x;
    this->y = y;
    this->z = z;

}

template <class T, size_t N>
Tuple<T, N>::Tuple( T x, T y, T z, T w )
{

    static_assert( N == 4, "This constructor is resererved for 4-tuples." );

    this->x = x;
    this->y = y;
    this->z = z;
    this->w = w;

}

// -- Access operators --

template <class T, size_t N>
const size_t Tuple<T, N>::size()
{

    return N;

}

template <class T, size_t N>
T &Tuple<T, N>::operator[]( const size_t i )
{

    assert( i < N );

    return ( &( this->x ) )[ i ];

}

template <class T, size_t N>
T const &Tuple<T, N>::operator[]( const size_t i ) const
{

    assert( i < N );

    return ( &( this->x ) )[ i ];

}

// -- Unary arithmetic operators --

template<class T, size_t N>
Tuple<T, N> &Tuple<T, N>::operator=( Tuple<T, N> const &t )
{

    for( size_t i = 0; i < size(); i++ )
    {

        this->operator[]( i ) = t[ i ];

    }

    return *this;

}

// -- Unary operators --

template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t )
{

    return t;

}

template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t )
{

    return t * T( 0.0f );

}

// -- Binary operators --

template<class T, size_t N>
Tuple<T, N> operator+( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{

    Tuple<T, N> sum;

    for( size_t i = 0; i < N; i++ )
    {

        sum[ i ] = t1[ i ] + t2[ i ];

    }

    return sum;

}

template<class T, size_t N>
Tuple<T, N> operator-( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{

    Tuple<T, N> difference;

    for( size_t i = 0; i < N; i++ )
    {

        difference[ i ] = t1[ i ] - t2[ i ];

    }

    return difference;

}

template<class T, size_t N>
Tuple<T, N> operator*( Tuple<T, N> const &t, T const &s )
{

    Tuple<T, N> product;

    for( size_t i = 0; i < N; i++ )
    {

        product[ i ] = t[ i ] * s;

    }

    return product;

}

template<class T, size_t N>
Tuple<T, N> operator*( T const &s, Tuple<T, N> const &t )
{

    Tuple<T, N> product;

    for( size_t i = 0; i < N; i++ )
    {

        product[ i ] = s * t[ i ];

    }

    return product;

}

template<class T, size_t N>
Tuple<T, N> operator/( Tuple<T, N> const &t, T const &s )
{

    assert( s != T( 0.0f ) );

    Tuple<T, N> quotient;

    T denom = T( 1.0f ) / s;

    for( size_t i = 0; i < N; i++ )
    {

        quotient[ i ] = t[ i ] * denom;

    }

    return quotient;

}

// -- Boolean operators --

template<class T, size_t N>
bool operator==( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{

    bool equal = true;

    for( size_t i = 0; i < N; i++ )
    {

        equal = ( t1[ i ] == t2[ i ] ) ? equal : false;

    }

    return equal;

}

template<class T, size_t N>
bool operator!=( Tuple<T, N> const &t1, Tuple<T, N> const &t2 )
{

    return !( t1 == t2 );

}

// -- Stream operator --

template <class T, size_t N>
inline std::ostream &operator<<( std::ostream &os, Tuple<T, N> const &t )
{

    os << "( ";

    for( size_t i = 0; i < t.size(); i++ )
    {

        os << t[ i ];

        if( i < t.size() - 1 )
        {

            os << ", ";

        }

    }

    os << " )";

    return os;

}

我不确定是否涵盖了所有运算符重载,但我想尽量简短。我也不满意构造函数的创建方式(也许可变参数模板可以解决问题)。至少它给了我以下理想的功能:

typedef Tuple<float, 2> Vec2;
typedef Tuple<float, 4> Vec4;
typedef Tuple<float, 10> Vec10;

Vec2 a( 1.0 );

a.x = 3.0f;
a.y = -3.0f;

assert( a[ 1 ] == -3.0f ); // this works

// a.z = 1.0f; ------> compiler error

Vec10 b;

b.x = 0.0f; b.y = 1.0f; b.z = 2.0f; b.w = 3.0f;

b[ 5 ] = 12.0f;

// etc...