Псевдоним переменной-члена в специализации шаблона класса - PullRequest
0 голосов
/ 03 января 2019

Предположим, я пишу шаблонный класс Vector для представления точек и векторов в N-мерном пространстве.Что-то вроде следующего:

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

Предположим далее, что по какой-то причине я хочу, чтобы пользователь имел возможность доступа к data со значимыми именами в случае меньших векторов, например, с помощью v.x или v.y вместо v.data[0] и v.data[1].

У меня есть два дополнительных ограничения.

  1. Доступ к компоненту x или y векторане должен быть записан как вызов функции (например, это должно быть v.x, а не v.x()).
  2. Следующее равенство должно выполняться sizeof(Vector<T, N>) == N * sizeof(T).

Я смотрел наразличные возможные подходы, в том числе ссылки на переменные-члены, отправка тегов и даже CRTP , но ни один из них не удовлетворяет всем моим требованиям.

Возможно ли даже создать такие псевдонимы?И если да, то как можно это сделать?

Ответы [ 3 ]

0 голосов
/ 03 января 2019

(Это не ответ, это комментарий с примером кода, который не подходит для комментария и плохо форматируется, если его можно вставить в комментарий.)

Можете ли вы пойти в другом направлении и выразить вектор как набор полей, а затем сопоставить индексный / установочный индекс с каждым из этих полей?

Извлечение параметра 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';
}
0 голосов
/ 06 января 2019

Если разрешено макросов , то это кажется выполнимым.

Первая попытка (хорошо, но не идеально ...)

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

РЕДАКТИРОВАТЬ : добавлен оператор [] для устранения проблемы, поднятой в комментарии.


Вторая попытка: с более хорошими именами полей

ОП запросил, чтобы у полей были более хорошие имена, такие как 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


Но подождите, оператор [] не так эффективен

У меня была мысль о более эффективном операторе [] в случае, если 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); \
        } \
    } \
}

Попытка (BAD): http://coliru.stacked -crooked.com / a / d367e770f107995f

К сожалению - приведенная выше оптимизация недопустима

Для представленной реализации любой Vector с DIMENSIONS> 1 равен не стандартным классом макета (даже если T равен) потому что он имеет члены как в базовом классе, так и в производном классе :

10.1 [class.prop]

[3] Класс S - это стандартная схемакласс, если он: ...

[3.6] содержит все нестатические члены-данные и битовые поля в классе, а его базовые классы впервые объявлены в том же классе ...

Таким образом, приведенная выше попытка оптимизации имеет неопределенное поведение - компилятор не обязан сохранять адрес членов в иерархии наследования в их порядке.

Первоначальное решение все еще действует.

0 голосов
/ 03 января 2019

Вот возможное решение (хотя я думаю, что это плохая практика, и я не совсем уверен, переносимо ли это):

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

Обновление: , и чтобы сделать это четко определенным , вы можете сделать:

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, z и T data[3], кажется, нена самом деле быть совместимым с макетом (см. здесь ).Тем не менее, этот шаблон, кажется, используется в различных библиотеках для реализации простых векторных типов - example1 (GLM) , example2 video , example3

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...