按值传递的积分常数,被视为constexpr?

时间:2017-09-18 22:18:41

标签: c++ templates c++14 constexpr

虽然我之前使用过这样的代码,但很明显编译器有足够的信息可以工作,但我真的不明白为什么这会编译:

template <class T, class I>
auto foo(const T& t, I i) {
    return std::get<i>(t);
}

int main()
{
    std::cerr << foo(std::make_tuple(3,4), std::integral_constant<std::size_t, 0>{});
    return 0;
}

实例:http://coliru.stacked-crooked.com/a/fc9cc6b954912bc5

似乎可以同时使用gcc和clang。问题是虽然integral_constant对存储的整数进行constexpr转换,但constexpr成员函数隐式地将对象本身作为参数,因此这样的函数不能用于{ {1}}上下文除非我们调用成员函数本身的对象可以被视为constexpr

此处,constexpr是传递给i的参数,因此foo肯定不能被视为i。然而,确实如此。一个更简单的例子:

constexpr

只要将template <class I> void foo(I i) { constexpr std::size_t j = i; } 传递给std::integral_constant<std::size_t, 0>{},就会编译。

我觉得我错过了foo规则的明显内容。无状态类型或其他类型是否有例外? (或者,也许是两个主要编译器中的编译器错误?这段代码似乎适用于clang 5和gcc 7.2)。

编辑:答案已经发布,但我认为这还不够。特别是,鉴于constexpr的最后定义,为什么:

foo

编译,但不是:

foo(std::integral_constant<std::size_t, 0>{});

0和foo(0); 都是常量表达式。

编辑2:看起来它归结为即使对不是常量表达式的对象调用std::integral_constant<std::size_t, 0>{}成员函数,也可以将其视为常量表达式,只要{{ 1}}未使用。这显然是显而易见的。我不认为这很明显:

constexpr

这不会编译,因为传递给this的{​​{1}}不是常量表达式。它没用就没关系。因此,完整的答案需要显示标准中的某种语言,以证明constexpr int foo(int x, int y) { return x; } constexpr void bar(int y) { constexpr auto x = foo(0, y); } 成员函数可以用作常量表达式,即使在非常量表达式对象上也是如此,只要{{ 1}}未使用。

2 个答案:

答案 0 :(得分:18)

编译时常量表达式的规则随constexpr改变,很多,但它们不是新的。在constexpr之前,已经存在编译时常量表达式......并且旧规则在新规范中作为特殊情况保留,以避免破坏大量现有代码。

在大多数情况下,旧规则处理积分类型的编译时常量aka 积分常量表达式 ...这正是您正在处理的情况。所以不,constexpr规则中没有任何奇怪的东西......其他较旧的规则与constexpr无关。

  

条件表达式e是核心常量表达式,除非根据抽象机器的规则评估e将评估以下表达式之一:

     

...

     
      
  • 一个左值到右值的转换,除非它适用于      
        
    • 整数或枚举类型的非易失性glvalue,它引用具有前面初始化的完整非易失性const对象,使用常量表达式初始化,或
    •   
    • 一个非易失性glvalue,它引用字符串文字的子对象,或
    •   
    • 一个非易失性glvalue,它引用一个用constexpr定义的非易失性对象,或者引用这种对象的不可变子对象,或者
    •   
    • 文字类型的非易失性glvalue,引用一个非易失性对象,其生命周期始于e的评估范围内;
    •   
  •   

你是对的,第三个子弹不适用。但第一个确实如此。

因此,新规则之间存在一个有趣的相互作用,它允许函数返回为编译时常量,具体取决于抽象机器上的评估规则,以及允许整数值为编译时常量的常规行为,即使不是标记为。

这是一个简单的例子,为什么this是一个隐含的参数并不重要。作为参数并不意味着评估对象:

constexpr int blorg(bool const flag, int const& input)
{
    return flag? 42: input;
}

int i = 5; // 5 is an integral constant expression, but `i` is not
constexpr int x = blorg(true, i); // ok, `i` was an argument but never evaluated
constexpr int y = blorg(false, i); // no way

对于std::integral_constant成员函数,您可以在*this函数中考虑iblorg一样 - 如果执行没有取消引用它,它就可以了传递而不是编译时常量。

答案 1 :(得分:12)

这是有效的原因:

self.SubViewModel = {
    treeViewOptions: {
        items: Stores,
        dataStructure: "plain",
        showCheckBoxesMode: true,
        searchValue: self.PackSearch,
        parentIdExpr: "Key",
        keyExpr: "ID",
        displayExpr: "Name",
        selectionMode: "single",
        expandAllEnabled: true,
        expandeExpr: "expanded",
        expandNodesRecursive: true,
    }
};

是因为它没有失败的原因适用。当template <class T, class I> auto foo(const T& t, I i) { return std::get<i>(t); } i时,std::integral_constant<size_t, S>可以用作i类型的转换常量表达式,因为该表达式遍历size_t,它只返回一个模板参数(prvalue)作为值。这是一个完全有效的常量表达式。请注意,constexpr operator size_t()未被引用 - 只是因为它的成员函数本身并不违反常量表达式约束。

基本上,没有什么&#34;运行时&#34;关于this这里。

另一方面,如果ii(通过int),则调用foo(0)将涉及{的{左值 - 右值转换{1}},但是这种情况不符合any of the criteria,因为std::get<i>没有先前的初始化与常量表达式,它不是字符串文字,它没有用{{定义1}},它并没有在这个表达式中开始它的生命周期。