【问题标题】:constexpr-ness of std::initializer_list::size() vs std::array::size()std::initializer_list::size() 与 std::array::size() 的 constexpr-ness
【发布时间】:2020-03-22 07:19:50
【问题描述】:

std::initializer_liststd::arraysize() 成员函数具有相同的签名:

constexpr size_type size() const noexcept;

两者都是constexpr。但是,std::array::size() 可以在 constexpr 上下文中使用,但 std::initializer_list::size() 不能:

std::initializer_list<int> il{1, 2, 3, 4};
constexpr std::size_t il_size = il.size();       // (1) - fails with GCC and Clang (*)

std::array<int, 4> arr{1, 2, 3, 4};
constexpr std::size_t arr_size = arr.size();     // (2) - OK

(*) 错误是:

in 'constexpr' expansion of 'il.std::initializer_list<int>::size()'
error: the value of 'il' is not usable in a constant expression

As far as I understand,(1)失败和(2)成功的事实是完全合理的,因为类模板的constexpr成员函数可能无法满足constexpr的要求。

我有两个相关的问题:

  1. 为什么std::initializer_list 没有以(1) 编译的方式实现?标准中有什么东西阻止了这种实现吗?
  2. 鉴于(1) 失败,将std::initializer_list::size() 标记为constexpr 的目的是什么?唯一的用例似乎是这个:

    constexpr std::initializer_list<int> il{1, 2, 3, 4};   // note constexpr
    constexpr std::size_t il_size = il.size();
    

【问题讨论】:

    标签: c++ c++17 constexpr initializer-list


    【解决方案1】:

    为什么std::initializer_list 不以(1)编译的方式实现?标准中有什么东西阻止了这种实现吗?

    是的,这是不可能的。 initializer_list 可以有任何大小,您不能在恒定的评估时间内获得任意运行时initializer_list 的大小。这与std::array 完全不同,其中给定的std::array&lt;T, N&gt; 的大小为N。一个的大小是可变的,另一个是固定的。

    这与任何其他变量并没有真正的不同:

    struct X { int i; };
    
    X x{42};
    constexpr X cx{17};
    
    constexpr int i = x.i;   // error
    constexpr int ci = cx.i; // ok
    

    鉴于 (1) 失败,将std::initializer_list::size() 标记为constexpr 的目的是什么?唯一的用例似乎是这个

    这不是唯一的用例,远非如此。 constexpr 成员函数不仅允许您在 constexpr 对象上调用它们。它们更普遍地允许您在持续评估时间内在任何地方调用它们。

    也就是说,在任何类型的持续评估期间,如果您创建一个initializer_list,您就可以使用它的大小。一个愚蠢的最小示例可能是:

    constexpr size_t four() {
        std::initializer_list<int> lst = {1, 2, 3, 4};
        return lst.size();
    }
    
    static_assert(four() == 4);
    

    请注意,lst 本身不是constexpr 对象,它只是在不断评估期间评估对four() 的调用期间创建的一些短暂的东西。但我们仍然需要将 size() 设为 constexpr - 调用任何非 constexpr 函数都是不行的。

    从这里你可以将它向外扩展到任何你可能想要在某个时刻运行的任意代码,在不断评估期间,想要确定std::initializer_list 的大小。

    【讨论】:

      【解决方案2】:

      并不是std::initializer_list::size不能用在常量表达式中。例如,以下可能(见脚注)should compile

      #include <initializer_list>
      int main() {
          constexpr int x = (std::initializer_list<int>{1, 2, 3, 4}).size();
          static_assert(x == 4);
      }
      

      但是,按照您的调用方式,可能发生的情况是 size 方法必须对 std::initializer_list 对象的成员执行左值到右值转换,这违反了对常量表达式的约束( C++17 [expr.const]/(2.7))。

      在我的示例中,相应的左值到右值的转换发生在“文字类型的非易失性左值,它引用一个非易失性对象,其生命周期开始于 e;" 的评估使其允许 (C++17 [expr.const]/(2.7.4))。

      std::array 示例中,可能没有左值到右值的转换,因为返回了模板参数。

      脚注:见LWG 2833

      【讨论】:

        猜你喜欢
        • 2016-01-03
        • 1970-01-01
        • 2021-03-19
        • 2021-11-16
        • 2016-05-23
        • 2011-09-24
        • 2021-09-28
        • 2018-06-29
        • 2012-04-01
        相关资源
        最近更新 更多