Использование std :: enable_if или аналогичного метода в переменной-члене - PullRequest
1 голос
/ 03 февраля 2020

В настоящее время у меня есть свой собственный векторный класс:

template<int D, typename T = float>
class Vec
{
    private:

        T data[D];

    public:

        Vec(T initial = 0)
        {
            for (int i = 0; i < D; ++i)
            {
                data[i] = initial;
            }
        }

        // Misc. operator overloads
};

using Vec2 = Vec<2>;
using Vec3 = Vec<3>;
using Vec4 = Vec<4>;

Я хотел бы добавить несколько переменных-членов, а именно x, y, z и w, это будет указывать на первую, вторую, третью и четвертую позиции в векторе соответственно. В идеале эти переменные были бы видимы, только если вектор был объявлен с достаточным количеством измерений. Т.е. двухмерный вектор не будет иметь доступа к Vec<D, T>::z.

То, что я нашел для работы:

template<int D2 = D, typename  T2 = typename std::enable_if<(D2 > 0), T*>::type>
T2 x(){ return &data[0];}
template<int D2 = D, typename  T2 = typename std::enable_if<(D2 > 1), T*>::type>
T2 y(){ return &data[1];}
template<int D2 = D, typename  T2 = typename std::enable_if<(D2 > 2), T*>::type>
T2 z(){ return &data[2];}
template<int D2 = D, typename  T2 = typename std::enable_if<(D2 > 3), T*>::type>
T2 w(){ return &data[3];}

Как вы можете видеть, с ++ будет немного кризис среднего возраста, если Я пытаюсь ввести параметры шаблона D и T как есть, поэтому я вынужден по существу переопределить каждый из них. И я знаю, что то, что я собираюсь сказать, смешно, но я бы очень хотел, чтобы x, y, z и w были переменными вместо функций, потому что, на мой взгляд, vec.x выглядит лучше, чем vec.x() (сумасшедший, я знаю).

Я не могу использовать это решение, потому что переменные не могут иметь шаблонов, только классы и функции. В настоящее время я играю с этим:

typename std::conditional<(D > 0), T*, std::nullptr_t>::type x = &data[0];
typename std::conditional<(D > 1), T*, std::nullptr_t>::type y = &data[1];
typename std::conditional<(D > 2), T*, std::nullptr_t>::type z = &data[2];
typename std::conditional<(D > 3), T*, std::nullptr_t>::type w = &data[3];

Я решил использовать std::nullptr_t в качестве запасного броска, потому что я хотел использовать тип с плавающей точкой, целые числа и так далее c. К сожалению, я пришел к выводу, что это не хорошо. Мне нужны средства для предотвращения оценки x, y, z и w до их вызова, в противном случае любой экземпляр Ve c с менее чем 4 измерениями вызовет ошибку компилятора строка, по которой оценивается w, то есть независимо от того, пытаюсь ли я вызвать w, она оценивается во время компиляции и выдаст ошибку.

Я ищу решение, которое либо препятствует оценке переменной, если она не вызывается (решение, которое не называется функцией), либо решение, которое позволит мне сделать переменную-член полностью невидимой вне класса при определенных c обстоятельствах.

Редактировать: у меня были дополнительные go, но безуспешно:

private:

        T data[D];

        auto getW(){return &data[4];};

    public:

        typename std::conditional<(D > 3), T*, std::nullptr_t>::type w = getW();

Это все равно вызывает ошибку компиляции для любого экземпляра class Vec с менее чем 4 измерениями , Теперь я действительно запутался. Я получаю ошибку cannot convert ‘float*’ to ‘std::conditional<false, float*, std::nullptr_t>::type’ {aka ‘std::nullptr_t}, но это не имеет никакого смысла, никогда не следует запускать getW(), никогда нельзя сравнивать float* и std::nullptr_t ...?

1 Ответ

3 голосов
/ 03 февраля 2020

Вам нужно специализироваться Vec, чтобы добавить эти переменные. Специализация всего класса Vec привела бы к большому количеству дублированного кода, поэтому вы можете перенести его в базовый класс, отвечающий за хранение данных вектора.

template<size_t D, typename T>
class VecStorage {
  T data[D];

public:
  T &operator[](const size_t i) {
    return const_cast<T &>(std::as_const(*this)[i]);
  }
  const T &operator[](const size_t i) const {
    assert(i < D);
    return data[i];
  }
};

template<typename T>
class VecStorage<2, T> {
public:
  T x, y;

  T &operator[](const size_t i) {
    return const_cast<T &>(std::as_const(*this)[i]);
  }
  const T &operator[](const size_t i) const {
    assert(i < 2);
    if (i == 0) {
      return x;
    } else {
      return y;
    }
  }
};

Я специализировался VecStorage для 2-х измерений. Вы можете добавить специализации для 3 и 4 измерений. Поскольку VecStorage предоставляет оператор индекса, Vec и пользователи Vec могут рассматривать его как массив.

template<size_t D, typename T = float>
class Vec : public VecStorage<D, T> {
public:
  explicit Vec(T initial = 0) {
    for (size_t i = 0; i != D; ++i) {
      (*this)[i] = initial;
    }
  }
};

int main() {
  Vec<2> v{42};
  std::cout << v.x << ' ' << v.y << '\n'; // 42 42
  v[0] = 5;
  v[1] = 7;
  std::cout << v.x << ' ' << v.y << '\n'; // 5 7
}
...