std :: Declare_if или другие гипотетические способы отбрасывания объявлений членов во время компиляции - PullRequest
2 голосов
/ 13 июня 2019

SFINAE весьма полезен для отбрасывания тел функций, но почему его нельзя использовать для отбрасывания переменных-членов?

Планируется ли когда-нибудь добавить такую ​​функциональность в современный C ++? Я попытался использовать std::enable_if, std::conditional (что сработало бы, если бы было разрешено иметь тип нулевого размера, но, вероятно, сломало бы все остальное).

Мне бы хотелось иметь возможность генерировать псевдонимы, используя гипотетический шаблон SFINAE, например:

template<class T, SIZE> 
struct Vector {
    union {
        T mArray[SIZE] = {};
        struct {
            std::declare_if<SIZE >= 1, T>::type x;
            std::declare_if<SIZE >= 2, T>::type y;
            std::declare_if<SIZE >= 3, T>::type z;
        };
    };
};

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

Ответы [ 2 ]

3 голосов
/ 14 июня 2019

С помощью атрибута [[no_unique_address]] вы можете достичь практически того, что вам нужно. в том числе:

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

Применительно к вашему варианту использования:

#include <type_traits>

template <typename T, int SIZE>
struct Vector
{
  T x;
  [[no_unique_address]] std::conditional_t<(SIZE > 1), T, decltype([]{})> y;
  [[no_unique_address]] std::conditional_t<(SIZE > 2), T, decltype([]{})> z;
};

int main()
{
  static_assert(sizeof(Vector<double, 1>) == 1 * sizeof(double));
  static_assert(sizeof(Vector<double, 2>) == 2 * sizeof(double));
  static_assert(sizeof(Vector<double, 3>) == 3 * sizeof(double));
}

Здесь я использовал decltype([]{}) как пустой тип, давая различные типы, чтобы они могли использовать один и тот же адрес.

2 голосов
/ 14 июня 2019

Сейчас это невозможно, но вы можете написать шаблонную функцию get(), которая принимает целочисленное значение. Кроме того, если вы используете C ++ 17, вы также можете использовать структурированное связывание.

#include <tuple>
#include <iostream>

// not elegant way of naming as enum will polute the whole namespace where it is defined
enum Idx {
    X = 0,
    Y = 1,
    Z = 2,
    W = 3,
    R = 0,
    G = 1,
    B = 2,
    A = 3
};

template <typename T, std::size_t SIZE>
struct Vector
{
    template<std::size_t Index>
    T& get() {
        static_assert(Index < SIZE, "Invalid Index");
        return data[Index];
    }

    template<std::size_t Index>
    const T& get() const noexcept {
        static_assert(Index < SIZE, "Invalid Index");
        return data[Index];
    }

    T data[SIZE];
};

//only needed if structured binding is required
namespace std {
template<typename T, size_t SIZE>
struct tuple_size<Vector<T, SIZE>> {
    constexpr static size_t value = SIZE;
};

template<typename T, size_t I, size_t SIZE>
struct tuple_element<I, Vector<T, SIZE>> {
    using type = T;
};
}

int main()
{
  Vector<int, 2> value = {0, 1};
  std::cout << "x = " << value.get<X>() << ": y = " << value.get<Y>() << '\n';

  // structured binding, available only in C++17
  auto& [x, y]  = value;
  std::cout << "x = " << x << ": y = " << y << '\n';

  // will generate a compiler error
  //auto& [x1, y1, z1] = value;

  // will invoke the static assert
  //auto z = value.get<Z>();
  return 0;
}
...