Реализация std :: array-like контейнера с C ++ 11 initializer_list - PullRequest
8 голосов
/ 13 августа 2011

Единственное и очень неудобное замечание std::array заключается в том, что он не может определить свой размер из списка инициализаторов, как встроенные массивы C, его размер должен быть передан как шаблон.

Можно ли реализовать контейнер типа std :: array (тонкая оболочка вокруг встроенного массива C) с C++11 initializer_list?

Я спрашиваю, потому что, в отличие от std :: array, он автоматически выводит размер массива из списка инициализаторов, что намного удобнее. Например:

// il_array is the hypothetical container
// automatically deduces its size from the initalizer list 
il_array <int> myarr = {2, 4, 6, 7, 8}; 

Мы также хотели бы предоставить конструктор для указания размера, если список инициализатора не был предоставлен. Например:

// construct a fixed size array of size 10
il_array <int> myarr2 (10); 

Это также сделает контейнер более совместимым с другими стандартными контейнерами, например, вектор, deque и список.

Насколько я знаю, это невозможно в качестве обернутого массива C, например. T elems [размер], должен иметь постоянный размер, а функция-член initializer_list size () не является постоянной.

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

Ответы [ 4 ]

3 голосов
/ 13 августа 2011

Я думаю, вам здесь не повезло.Большим преимуществом std :: array является то, что он является POD и может быть статически инициализирован.

Если у вас есть контейнер с конструктором, принимающим std :: initializer_list, ему придется копировать значенияэто просто постоянная ссылка на инициализатор, что не очень полезно).

2 голосов
/ 23 декабря 2011

Можно ли реализовать контейнер типа std :: array (тонкая оболочка вокруг встроенного массива C) с C ++ 0x initializer_list?

Да , ну, пока вы готовы обманывать. Как отмечает Mooing Duck, нет, даже не обманывать, , если разработчики компилятора не позволят вам . Тем не менее, все еще возможно подойти достаточно близко - можно использовать списки инициализаторов и статический массив, который скрыт оболочкой.

Это некоторый код, который я написал для моей личной панели инструментов. Ключ должен полностью игнорировать размер, даже для массива, и позволить контейнеру провайдера обрабатывать его; в этом случае, initializer_list, который может предоставить размер через std::distance, таким образом избегая экспликации размера на стороне клиента (кажется, термин, который я только что изобрел).

Поскольку это код типа «любой мог придумать это», нет проблем с его предоставлением «обратно» для публики; На самом деле, я получил идею от какого-то опытного парня, чей ник я не помню на канале Freenode ##c++, так что, я думаю, признание для них:

* EDIT * изд:

template <typename T> struct carray {
    // typedefs for iterator. The best seems to be to use std::iterator<std::random_iterator_tag,T,int> here
    ...

    template <size_t N> 
    explicit carray (T (&arr)[N]) 
    : ax(arr), sx(N) {}

    // note the linked article. 
    // This works *only* if the compiler implementor lets you. 
    carray (std::initializer_list<T> X) 
    : ax (X.begin()), sx(std::distance(X.begin(),X.end()) {}

    // YMMV about the rest of the "rule of N":
    // no copy constructor needed -- trivial
    // no destructor needed -- data outscopes the wrapper
    // no assignment operator needed -- trivial

    // container functions, like begin(), end(), size()...

    private:
    T* ax;
    size_t const sx;
};

Использование и объявление в режиме C ++ 0x довольно просто (только что протестировано с GCC 4.6 в Fedora 15), но оно работает с предостережениями, отмеченными во внешних ссылках выше, так что это явно неопределенное поведение:

using lpp::carray;
carray<short const> CS = {1, -7, 4, 188};

Однако я не вижу , почему разработчик компилятора не реализовал бы initializer_list интегралов как статический массив в любом случае. Ваш звонок.

Мало того, что он работает таким образом, при условии, что вы можете #ifdef создать конструктор инициализатора в обычном режиме до C ++ 0x, вы можете использовать его в pre-C ++ 0x; хотя предварительное объявление массива данных в качестве его собственной переменной потребуется, IMHO, оно ближе всего к первоначальному намерению (и оно имеет преимущество в том, что его можно использовать, а не вызывать, например, проблемы с областью видимости). (также протестировано с помощью вышеуказанного компилятора плюс GCC Debian Wheezy):

using lpp::carray;
short data[]= {1, -7, 4, 188};
carray<short const> CS (data);

Там! Нет параметра "size" в любом месте !

Мы также хотели бы предоставить конструктор для указания размера, если список инициализатора не был предоставлен.

Извините, это одна функция, которую я не реализовал. Проблема в том, как назначить память «статически» из внешнего источника, возможно, из Allocator. Если предположить, что это можно сделать как-нибудь через вспомогательный функтор allocate, тогда конструктор будет выглядеть примерно так:

explicit carray (size_t N)
: ax(allocate(N)), sx(N) {}

Надеюсь, этот код поможет, так как я вижу вопрос более или менее старый.

1 голос
/ 29 декабря 2011

Как насчет этого?Я использовал std::tuple вместо initializer_list, потому что число аргументов кортежа доступно во время компиляции.Класс tuple_array ниже наследуется от std::array и добавляет шаблонный конструктор, который предназначен для использования с std::tuple.Содержимое кортежа копируется в базовое хранилище массива с помощью метапрограммы Assign, которая просто выполняет итерацию от N до 0 во время компиляции.Наконец, функция make_tuple_array принимает произвольное количество параметров и создает tuple_array.Тип первого аргумента считается типом элемента массива.Хорошие компиляторы должны исключить лишнюю копию, используя RVO.Программа работает на g ++ 4.4.4 и 4.6.1 с RVO.

#include <array>
#include <tuple>
#include <iostream>

template <size_t I, typename Array, typename Tuple>
struct Assign
{
  static void execute(Array &a, Tuple const & tuple)
  {
    a[I] = std::get<I>(tuple);
    Assign<I-1, Array, Tuple>::execute(a, tuple);
  }
};

template <typename Array, typename Tuple>
struct Assign <0, Array, Tuple>
{
  static void execute(Array &a, Tuple const & tuple)
  {
    a[0] = std::get<0>(tuple);
  }
};

template <class T, size_t N>
class tuple_array : public std::array<T, N>
{
    typedef std::array<T, N> Super;

  public:

    template<typename Tuple>
    tuple_array(Tuple const & tuple)
      : Super()
    {
      Assign<std::tuple_size<Tuple>::value-1, Super, Tuple>::execute(*this, tuple);
    }
};

template <typename... Args>
tuple_array<typename std::tuple_element<0, std::tuple<Args...>>::type, sizeof...(Args)>
make_tuple_array(Args&&... args)
{
  typedef typename std::tuple_element<0, std::tuple<Args...>>::type ArgType;
  typedef tuple_array<ArgType, sizeof...(Args)> TupleArray;
  return TupleArray(std::tuple<Args...>(std::forward<Args>(args)...));
}

int main(void)
{
  auto array = make_tuple_array(10, 20, 30, 40, 50);
  for(size_t i = 0;i < array.size(); ++i)
  {
    std::cout << array[i] << " ";
  }
  std::cout << std::endl;

  return 0;
}
0 голосов
/ 30 декабря 2011

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

// il_array is the hypothetical container
// automatically deduces its size from the initalizer list 
il_array <int> myarr = {2, 4, 6, 7, 8}; 

Попробуйте это:

// il_array is the hypothetical container
// automatically deduces its size from the initalizer list 
std::initalizer_list<int> myarr = {2, 4, 6, 7, 8}; 

Это копирует? В самом техническом смысле ... да. Однако копирование списка инициализатора, в частности , не копирует его содержимое. Так что это стоит всего лишь пару копий указателя. Кроме того, любой компилятор C ++, который стоит использовать, превратит эту копию в ничто.

Итак, у вас есть это: массив, размер которого известен (через std::initializer_list::size). Ограничения здесь:

  1. размер недоступен во время компиляции.
  2. массив не изменяемый.
  3. std::initializer_list довольно голые. У него даже нет оператора [].

Третий, наверное, самый раздражающий. Но это также легко исправить:

template<typename E> class init_array
{
public:
  typedef std::initializer_list<E>::value_type value_type;
  typedef std::initializer_list<E>::reference reference;
  typedef std::initializer_list<E>::const_reference const_reference;
  typedef std::initializer_list<E>::size_type size_type;

  typedef std::initializer_list<E>::iterator iterator;
  typedef std::initializer_list<E>::const_iterator const_iterator;

  init_array(const std::initializer_list<E> &init_list) : m_il(init_list) {}

  init_array() noexcept {}

  size_t size() const noexcept {return m_il.size();}
  const E* begin() const noexcept {return m_il.begin();}
  const E* end() const noexcept {return m_il.end();}

  const E& operator[](size_type n) {return *(m_il.begin() + n);} 
private:
  std::initializer_list m_il;
};

Там; задача решена. Конструктор списка инициализатора гарантирует, что вы можете создать его непосредственно из списка инициализатора. И хотя копия больше не может быть удалена, она все равно просто копирует пару указателей.

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