9

唯一且 imo 非常不方便的警告std::array是,它不能像内置 C 数组那样从初始化列表中推断出它的大小,它的大小必须作为模板传递。

是否可以使用 initializer_list 实现类似 std::array 的容器(内置 C 数组的薄包装器)C++11

我问是因为,与 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); 

这也将使容器与其他标准容器(例如向量、双端队列和列表)更加一致。

据我所知,这是不可能的,因为包装的 C 数组,例如 T elems [size],必须具有恒定的大小,并且 initializer_list 的 size() 成员函数不是恒定的。

另外,我想知道是否可以使用可变参数模板来实现这样的容器,尽管从我读过的内容来看,我认为这是不可能的。

4

4 回答 4

3

I think you are out of luck here. The great advantage of std::array is that it is a POD and can be statically initialized.

If you have a container with a constructor taking a std::initializer_list, it would have to copy the values (unless it is just a constant reference to the initializer, which isn't very useful).

于 2011-08-13T19:23:57.997 回答
2

是否可以使用 C++0x initializer_list 实现类似 std::array 的容器(内置 C 数组的薄包装器)?

的,好吧,只要你愿意作弊。正如 Mooing Duck 指出的那样,不,甚至不作弊,除非编译器实现者允许。尽管如此,仍然可以足够接近——可以使用初始化列表和被包装器隐藏的静态数组。

这是我为我的个人工具箱编写的一些代码。关键是完全忽略大小,即使是数组,让提供者容器处理它;在这种情况下,initializer_list谁可以通过 提供大小std::distance,从而避免客户端大小显式化(似乎是我刚刚发明的一个术语)。

由于它是“任何人都可以想出”的代码,因此将其“返回”给公众没有问题;事实上,我从某个专家那里得到了这个想法,我不记得他在 Freenode 的##c++频道上的昵称,所以我想这是对他们的认可:

*编辑*编:

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 模式下的使用和声明非常简单(刚刚在 Fedora 15 中使用 GCC 4.6 进行了测试),但它适用于上面外部链接中提到的警告,因此它显然是未定义的行为:

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

但是,我不明白为什么编译器实现者无论如何都不会将积分的 initializer_list 实现为静态数组。你的来电。

不仅如此,只要你可以#ifdef在 pre-C++0x 模式下使用初始化构造器,你实际上可以在 pre-C++0x 中使用它;尽管需要将数据数组预先声明为其自己的变量,但恕我直言,它最接近原始意图(并且它具有可用且不会导致例如:范围问题的优点)。(也用上面的编译器测试过,加上 Debian Wheezy 的 GCC):

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

那里!任何地方都没有“大小”参数!

如果没有提供初始化列表,我们还想提供一个构造函数来指定大小。

抱歉,这是我尚未实施的一项功能。问题是如何从外部源(可能是分配器)“静态”分配内存。假设它可以通过辅助函子以某种方式完成allocate,那么构造函数将是这样的:

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

我希望这段代码有帮助,因为我看到这个问题或多或少是旧的。

于 2011-12-22T20:54:52.310 回答
1

这个怎么样?我使用std::tuple而不是 aninitializer_list因为元组参数的数量在编译时可用。下面的tuple_array类继承自std::array并添加了一个模板化构造函数,该构造函数旨在与std::tuple. 使用元程序将元组的内容复制到底层数组存储中Assign,该程序在编译时简单地从 N 迭代到 0。最后,该make_tuple_array函数接受任意数量的参数并构造一个tuple_array. 假设第一个参数的类型是数组的元素类型。好的编译器应该使用 RVO 消除额外的副本。该程序适用于带有 RVO 的 g++ 4.4.4 和 4.6.1。

#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;
}
于 2011-12-29T03:47:18.447 回答
0

我觉得这个问题真的很简单。您需要一种类型,该类型的大小将调整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;
};

那里; 问题解决了。初始化列表构造函数确保您可以直接从初始化列表创建一个。虽然副本不能再被省略,但它仍然只是复制一对指针。

于 2011-12-29T20:35:50.443 回答