【问题标题】:How do I initialize a member array with an initializer_list?如何使用 initializer_list 初始化成员数组?
【发布时间】:2011-07-29 19:16:20
【问题描述】:

我正在加快使用 C++0x 的速度,并使用 g++ 4.6 进行测试

我刚刚尝试了以下代码,认为它可以工作,但它无法编译。我得到了错误:

incompatible types in assignment of ‘std::initializer_list<const int>’ to ‘const int [2]’

struct Foo
  {
    int const data[2];

    Foo(std::initializer_list<int const>& ini)
    : data(ini)
    {}
  };

Foo f = {1,3};

【问题讨论】:

    标签: c++ arrays c++11 initializer-list


    【解决方案1】:

    你不能,数组不像其他类型(并且没有构造函数采用 std::initializer_list)。

    试试这个:

    struct Foo  
    {  
      const std::vector<int>   data;
      Foo(std::initializer_list<int> ini) : data(ini)
      {}
    }; 
    

    【讨论】:

    • 我认为它也可以与 std::array 一起使用以更接近 OPs 的原始实现。
    • @ronag:我认为 std::array 根本没有任何构造函数,它应该像 C 样式数组一样被初始化。
    • 唉,我的预期用途是在 std::vector 的开销不可接受的情况下。 std::array 会很好,但它有完全相同的问题。
    • C++0x std::array 真的应该有一个初始化列表构造函数,以及一个 [begin, end) 构造函数。 boost/tr1 实现没有的原因,源于 C++03 中不再存在的 C++0x 限制。
    • @Johannes: std::tuple 也是。这让我很难过。
    【解决方案2】:

    您可以使用可变参数模板构造函数代替初始化列表构造函数:

    struct foo { 
        int x[2]; 
        template <typename... T> 
        foo(T... ts) : x{ts...} { // note the use of brace-init-list
        } 
    };
    
    int main() {
        foo f1(1,2);   // OK
        foo f2{1,2};   // Also OK
        foo f3(42);    // OK; x[1] zero-initialized
        foo f4(1,2,3); // Error: too many initializers
        foo f5(3.14);  // Error: narrowing conversion not allowed
        foo f6("foo"); // Error: no conversion from const char* to int
    }
    

    编辑:如果你可以不使用 constness, 另一种方法是跳过初始化并在函数体中填充数组:

    struct foo {
        int x[2]; // or std::array<int, 2> x;
        foo(std::initializer_list<int> il) {
           std::copy(il.begin(), il.end(), x);
           // or std::copy(il.begin(), il.end(), x.begin());
           // or x.fill(il.begin());
        }
    }
    

    但是,这样一来,您就失去了前一个解决方案提供的编译时边界检查。

    【讨论】:

    • 我认为他希望数组为const...第一个解决方案满足这一点,但不是第二个。
    • 是的,我更希望编译器会根据数据的常量性进行一些优化。尽管如此,我还是赞成第一个解决方案,因为我很喜欢它。您的方法二是我在发布此问题时决定使用的解决方法,但我宁愿不必走那条路。
    • @swestrup: const 通常不会通知优化;编译器通过变量生命周期分析来计算这些东西。 const 是一种正确性检查,有助于语言支持只读内存。
    • 可变参数模板构造函数可能与其他构造函数冲突,所以我认为它不是解决 OP 问题的通用解决方案(尽管它可能只在这种特殊情况下起作用)。
    • 在第二个示例中,std::copy(x, x+2, il.begin()); 行应该是 std::copy(il.begin(), il.end(), x);,因为我们想从 il 初始化 x[2],不是吗?
    【解决方案3】:

    据我所知,使用构造函数(8.5.4/1)的函数参数的列表初始化应该是合法的,并且可以解决上述许多问题。但是,ideone.com 上的 GCC 4.5.1 无法匹配构造函数并拒绝它。

    #include <array>
    
    struct Foo
      {
        std::array< int, 2 > const data;
    
        Foo(std::array<int, 2> const& ini) // parameter type specifies size = 2
        : data( ini )
        {}
      };
    
    Foo f( {1,3} ); // list-initialize function argument per 8.5.4/1
    

    如果你真的坚持initializer_list,可以使用reinterpret_castinitializer_list的底层数组变成C风格的数组。

    Foo(std::initializer_list<int> ini) // pass without reference- or cv-qualification
    : data( reinterpret_cast< std::array< int, 2 > const & >( * ini.begin() )
    

    【讨论】:

    • 我想投赞成票和反对票,但他们不让。
    • 哎呀。我既震惊又感动。我真的希望有一种不那么骇人听闻的方式来做到这一点。
    • @Tony:我不怪你,但一个人必须勇敢地驾驭邪恶的力量……对吧?
    • 后一种解决方案应该 AFAICS 确实有效:函数参数传递是直接初始化上下文;列表初始化可以发生在直接初始化中;对聚合进行列表初始化会导致聚合初始化(8.5.4/3)发生;并且 std::array 是设计上的聚合。似乎是 GCC 4.5 中的一个错误。
    • 我更喜欢你的第二个解决方案,但是 g++ 4.6 也不接受它。
    【解决方案4】:

    根据讨论here

    Potatoswatter 的第二种解决方案的正确语法是:

    Foo f( {{1,3}} ); //two braces
    

    有点丑,不符合常用用法

    【讨论】:

      【解决方案5】:

      只是对伟大的JohannesD answer 的一个小补充。

      如果没有参数传递给foo 构造函数,数组将被默认初始化。但有时您希望保持底层数组未初始化(可能出于性能原因)。您不能将默认构造函数与可变参数模板一起添加。 解决方法是可变参数模板构造函数的附加参数,以将其与零参数构造函数区分开来:

      template<class T, size_t rows, size_t cols>
      class array2d
      {
          std::array<T, rows * cols> m_Data;
          public:
      
          array2d() {}
      
          template <typename T, typename... Types>
          array2d(T t, Types... ts) : m_Data{ { t, ts... } } {}
      };
      

      所以,现在您可以对对象进行大括号初始化,或者让它未初始化:

      array2d<int, 6, 8> arr = { 0, 1, 2, 3 };  // contains 0, 1, 2, 3, 0, 0, 0, ...
      array2d<int, 6, 8> arr2;                  // contains garbage
      

      2016 年 7 月 31 日更新

      三年过去了,编译器实现者将其产品的标准合规性提高到了默认构造函数在可变参数构造函数的存在下不再被视为模棱两可的水平。因此,在实践中,我们不需要为可变参数构造函数添加额外的T t 参数来消除构造函数的歧义。

      两者

      array2d() {}
      

      array2d() = default;
      

      如果对象是在没有参数的情况下构造的,则数组将保持未初始化状态。这种行为在所有主要编译器上都是一致的。完整示例 (rextester):

      #include <array>
      #include <iostream>
      
      template<class T, size_t rows, size_t cols>
      class array2d
      {
        public:
          std::array<T, rows * cols> m_Data;
      
          array2d() = default;
      
          template <typename... Types>
          array2d(Types... ts) : m_Data{ { ts... } } {}
      };
      
      int main()
      {
          array2d<int, 6, 8> arr_init = { 0, 1, 2, 3 };
          array2d<int, 6, 8> arr_default;
      
      
          std::cout << "Initialized: \n";
          for(const auto& a : arr_init.m_Data)
              std::cout << a << " ";
          std::cout << "\n";
      
          std::cout << "Default: \n";
          for(const auto& a : arr_default.m_Data)    
              std::cout << a << " ";
      
          std::cout << "\n";
      }
      

      输出:

      Initialized: 
      0 1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
      Default: 
      2 0 -519559849 32558 1 32558 0 0 -519634912 32558 -526739248 32558 1 0 2 0 6295032 0 -519531243 32558 0 0 -1716075168 32765 6295648 0 4196192 0 6295648 0 -526527271 32558 1 0 2 0 6295032 0 4196845 0 124 0 0 0 4196768 0 4196518 0 
      

      删除默认构造函数仍然会导致调用可变参数构造函数并且数组被默认初始化(在我们的例子中是全零)。

      感谢@Alek 提出这个话题并引起人们对这些事实的关注,同时也感谢所有致力于编译器开发的人。

      【讨论】:

      • "您不能将默认构造函数与可变参数模板一起添加。"仅当默认构造函数是隐式的时,这才是正确的,这不是您在答案中建议的。 stackoverflow.com/a/2953925/259543
      • @Alek 确实如此。现在它在实践中也有效。我更新了我的答案。太糟糕了,我不记得以前触发歧义的编译器和编译器版本。谢谢你的评论。
      【解决方案6】:

      虽然这不起作用:

      #include <initializer_list>
      struct Foo
      {
        const int data[2];
      
        constexpr Foo(const std::initializer_list<int>& ini): data{ini}  {}
      };
      
      Foo f = {1,3};
      

      我发现这种简单的方法效果很好:

      struct Foo
      {
        const int data[2];
      
        constexpr Foo(const int a, const int b): data{a,b}  {}
      };
      
      Foo f = {1,3};
      

      当然,如果您有很多元素,可变参数模板方法可能会更好,但在这种简单的情况下,这可能就足够了。

      也就是说,如果您想从初始值设定项列表中显式定义构造函数。对于大多数 POD 案例来说,这很好而且很花哨:

      struct Foo
      {
        const int data[2];
      };
      Foo f = {1,3};
      

      【讨论】:

        【解决方案7】:

        如果您不关心边界检查,那么以下方法将起作用。

        struct Foo {
            int const data[2];
            Foo(std::initializer_list<int> ini)
                : data{*std::begin(ini), *std::next(std::begin(ini), 1)} {}
        };
        

        【讨论】:

          【解决方案8】:

          您可以定义一个constexpr 函数,将初始值设定项列表转换为数组。最后一个(第三个)函数是您调用的函数。另一个从初始化列表递归创建模板参数包,并在读取足够多的列表元素后创建数组。

          template <typename T, size_t N, typename... Ts>
          constexpr enable_if_t<(sizeof...(Ts) == N), array<T, N> >
          array_from_initializer_list(const T *const beg, const T *const end,
                                      const Ts... xs) {
            return array<T, N>{xs...};
          }
          
          template <typename T, size_t N, typename... Ts>
          constexpr enable_if_t<(sizeof...(Ts) < N), array<T, N> >
          array_from_initializer_list(const T *const beg, const T *const end,
                                      const Ts... xs) {
            return array_from_initializer_list<T, N>(beg + 1, end, *beg, xs...);
          }
          
          template <typename T, size_t N>
          constexpr array<T, N> array_from_initializer_list(initializer_list<T> l) {
            return array_from_initializer_list<T, N>(l.begin(), l.end());
          }
          

          【讨论】:

            猜你喜欢
            • 2019-07-02
            • 2019-04-16
            • 1970-01-01
            • 2018-06-22
            • 2021-03-26
            • 1970-01-01
            • 2015-11-28
            • 1970-01-01
            • 2015-02-07
            相关资源
            最近更新 更多