类模板专业化中的成员变量别名

时间:2019-01-03 18:47:25

标签: c++ templates alias template-specialization member-variables

让我们假设我正在编写一个Vector模板类来表示N维空间中的点和向量。类似于以下内容:

template <typename T, int N>
struct Vector
{
    T data[N];
    // ...
};

让我们进一步假设,无论出于何种原因,我都希望用户在较小的矢量(例如)的情况下能够使用有意义的名称访问data。通过使用v.xv.y而不是v.data[0]v.data[1]

我还有两个约束。

  1. 对向量的xy组件的访问不应写为函数调用(例如,应为v.x,而不是v.x())。
  2. 以下等式必须包含sizeof(Vector<T, N>) == N * sizeof(T)

我研究了各种可能的方法,包括成员变量引用,标签分发,甚至还有 CRTP ,但是没有一个方法满足我的所有要求。

甚至可以创建这样的别名吗?如果是的话,怎么办呢?

3 个答案:

答案 0 :(得分:2)

(这不是答案,它是带有代码示例的注释,不适合作为注释,并且如果可以将其填充到注释中,格式也不正确。)

您可以走另一个方向,将向量表示为一堆字段,然后将索引获取器/设置器映射到每个字段吗?

取出N模板参数来简化问题:

#include <iostream>
#include <stdexcept>

template <typename T>
struct Vector3
{
    T x;
    T y;
    T z;
    T operator[](int i) const
    {
        switch(i)
        {
            case 0:
                return x;
            case 1:
                return y;
            case 2:
                return z;
            default:
                throw std::out_of_range("out of range");
        }
    }
    T& operator[](int i)
    {
        switch(i)
        {
            case 0:
                return x;
            case 1:
                return y;
            case 2:
                return z;
            default:
                throw std::out_of_range("out of range");
        }
    }
};

int main()
{
    Vector3<float> v;
    v.x = 1.0f;
    v[1] = 2.0f;
    v.z = 3.0f;
    std::cout << v[0] << " " << v.y << " " << v[2] << '\n';
}

答案 1 :(得分:1)

如果允许,那么它似乎可行。

第一次尝试(不错,但不是完美的……)

int main() {
    Vector<int, 4> vec;
    vec[0] = 1; // same as: vec.t1 = 1;
    vec[1] = 2; // same as: vec.t2 = 2;
    vec[2] = 3; // same as: vec.t3 = 3;
    vec[3] = 4; // same as: vec.t4 = 4;
    std::cout << vec.t1 + vec.t2 + vec.t3 + vec.t4; // 10
}

要实现上述目标:

#define VAR_NAME(num) t##num

#define DefineVector(num) \
    template<typename T> \
    struct Vector<T, num> : Vector<T, num-1> { \
        T VAR_NAME(num); \
        T& operator[](int index) { \
            if(index == num-1) return VAR_NAME(num); \
            return Vector<T, num-1>::operator[](index); \
        } \
    }

template<typename T, size_t N>
struct Vector;

template<typename T>
struct Vector<T, 1> {
    T t1;
    T& operator[](int index) {
        // in case index != 0 this is UB
        return t1;
    }
};

DefineVector(2);
DefineVector(3);
DefineVector(4);

// TODO:
// replace 3 declarations above with a single *DefineVectorsRecursively(4);*
// by using recursive macros
// see: https://stackoverflow.com/questions/12447557/can-we-have-recursive-macros
// leaving this as a further exercise...

http://coliru.stacked-crooked.com/a/42625e9c198e1e58

编辑:添加了运算符[],以解决评论中提出的问题。


第二次尝试:使用更好的字段名称

OP要求字段具有更好的名称,例如x,y,z。

这是一个挑战。但是,宏又再次发挥了作用:

int main() {
    Vector<int, 3> vec;
    vec[0] = 1;
    vec[1] = 2;
    vec[2] = 3;
    std::cout << vec.x + vec.y + vec.z; // 6
}

使用以下代码:

#include <boost/preprocessor/variadic/size.hpp>

template<typename T, size_t DIMENSIONS>
struct Vector;

#define DefineVector(VAR, ...) \
    template<typename T> \
    struct Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) + 1> \
      : Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)> { \
        T VAR; \
        T& operator[](int index) { \
            if(index == BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) return VAR; \
            return Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)>::operator[](index); \
        } \
    }

#define DefineVector1(VAR) \
    template<typename T> \
    struct Vector<T, 1> { \
        T VAR; \
        T& operator[](int index) { \
            /* in case index != 0 this is UB */ \
            return VAR; \
        } \
    }

DefineVector1(x);
DefineVector(y, x);
DefineVector(z, y, x);
// TODO: create recursive macro for DefineVector(z, y, x)
// that will create the two above recursively

代码:http://coliru.stacked-crooked.com/a/2550eede71dc9b5e


但是等等,operator []效率不高

在T为 标准布局类型 的情况下,我想到了一种更高效的运算符[],并实现以下操作:

#define DefineVector(VAR, ...) \
    template<typename T> \
    struct Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) + 1> \
      : Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)> { \
        T VAR; \
    T& operator[](int index) { \
        if constexpr(std::is_standard_layout_v<T>) { \
            return *(&VAR - (BOOST_PP_VARIADIC_SIZE(__VA_ARGS__) - index)); \
        } else { \
            if(index == BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)) return VAR; \
            return Vector<T, BOOST_PP_VARIADIC_SIZE(__VA_ARGS__)>::operator[](index); \
        } \
    } \
}

(不良)尝试:http://coliru.stacked-crooked.com/a/d367e770f107995f

不幸的是,以上优化是非法的

对于所提供的实现,任何DIMENSIONS> 1的Vector都是 不是标准布局类 (即使T为because it has members both in base class and in the derived class

  

10.1 [class.prop]

     

[3]如果满足以下条件,则S类为标准布局类:...

     

[3.6]在类及其基类中具有所有非静态数据成员和位字段   首先在同一类中声明的类...

因此,上面的优化尝试具有未定义的行为-编译器没有义务按继承顺序保留成员在继承层次结构中的地址。

初始解决方案仍然有效。

答案 2 :(得分:-1)

这里是可能的解决方案(尽管我认为这是不好的做法,并且不确定是否可移植):

template <typename T, int N>
union Vector
{
    struct { T x, y, z; };
    T data[N];
};

以下是发生的示例:

int main() {
    Vector<int, 10> vec;
    vec.x = 100;
    vec.y = 200;
    vec.z = 300;
    vec.data[3] = vec.data[2] + 100;

    printf("%d %d %d %d\n", vec.data[0], vec.data[1], vec.data[2], vec.data[3]);
    printf("size = %d\n", (int) sizeof(vec));

    return 0;
}

Output:
    100 200 300 400
    size = 40

更新:要进行此well defined,您可以执行以下操作:

template <typename T, int N> union Vector;

template <typename T> union Vector<T, 1> {
    struct { T x; };
    T data[1];
};

template <typename T> union Vector<T, 2> {
    struct { T x, y; };
    T data[2];
};

template <typename T> union Vector<T, 3> {
    struct { T x, y, z; };
    T data[3];
};

template <typename T> union Vector<T, 4> {
    struct { T x, y, z, w; };
    T data[4];
};

只需确保struct是标准版式(即适用于T = int,float,double等)。


更新2:请注意,以上内容仍可能是UB,因为T x, y, zT data[3]似乎实际上与布局不兼容(请参阅here) 。尽管如此,该模式似乎仍在各种库中用于实现简单的向量类型-example1 (GLM)example2 videoexample3