通过值传递的积分常量,视为constexpr? [英] Integral constant passed by value, treated as constexpr?

查看:107
本文介绍了通过值传递的积分常量,视为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> {});
返回0;
}

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



似乎与gcc和clang一起使用。事实是,虽然 integral_constant 具有 constexpr 到存储整数的转换,但 constexpr 成员函数隐式地将对象本身作为参数,因此,除非我们正在调用该对象的对象,否则该函数不能在 constexpr 上下文中使用。函数本身可以视为 constexpr



此处, i 是传递给 foo 的参数,因此 i 最肯定不能被视为 constexpr 。但是,确实如此。甚至更简单的示例:

  template< class I> 
void foo(I i){
constexpr std :: size_t j = i;
}

只要 std:也可以编译integer_constant< std :: size_t,0> {} 传递给 foo



我觉得我缺少有关 constexpr 规则的明显信息。无状态类型是否有例外? (或者,也许是两个主要编译器中的编译器错误?此代码似乎可以在clang 5和gcc 7.2上运行)。



编辑:已经发布了答案,但是我认为这还不够。特别是,给定 foo 的最后定义,为什么这样做:

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

编译,但不:

  foo(0); 

0和 std :: integral_constant< std :: size_t,0> {} 是常量表达式。



编辑2:似乎可以归结为调用 constexpr 成员函数即使在不是常量表达式的对象上,只要未使用 this 本身也可以视为常量表达式。这是显而易见的。我不认为这很明显:

  constexpr int foo(int x,int y){return x; } 

constexpr void bar(int y){constexpr auto x = foo(0,y); }

这不会编译,因为 y 传递给 foo 的不是常量表达式。不用也没关系。因此,完整的答案需要显示标准中的某种语言,以证明 constexpr 成员函数可以用作常量表达式的事实,即使在非常量表达式对象,只要未使用 this

解决方案

constexpr (很多)更改了编译时常量表达式的规则,但这并不是新的。在 constexpr 之前,已经有编译时常量表达式...并且在新规范中将旧规则保留为特殊情况,以避免破坏大量现有代码。 / p>

在大多数情况下,旧规则处理整数类型又称为整数常量表达式的编译时常量...正是这种情况您正在处理。因此,不, constexpr 规则中没有任何奇怪的东西……其他更旧的规则与 constexpr <没有关系/ code>。


条件表达式e是一个核心常数表达式,除非遵循e的规则对e求值。抽象机,将评估以下表达式之一:



...




  • 从左值到右值的转换,除非将其应用于


    • 整数或枚举类型的非易失性glvalue,它表示完整的非易失性const带有先前初始化的对象,该对象使用常量表达式初始化,或者

    • 引用字符串文字的子对象的非易失性glvalue或

    • 一个非易失性glvalue,它引用用 constexpr 定义的一个非易失性对象,或者引用该对象的一个​​非可变子对象,或者

    • 非v文字类型的非易失性glvalue,指的是一个非易失性对象,其寿命在e的求值内开始;



您说对了,第三个子项目不适用。但是,第一个是。



因此,在新规则之间有一个有趣的相互作用,这些新规则允许函数返回值是编译时常量,具体取决于抽象上求值的规则。机器,以及即使未标出整数值也允许编译时常数保持不变的传统行为。






一个简单的示例,为什么 this 是隐式参数并不重要。成为参数并不意味着会评估对象:

  constexpr int blorg(bool const flag,int const& input)
{
返回标志? 42:输入;
}

int i = 5; // 5是整数常量表达式,但是`i`不是
constexpr int x = blorg(true,i); //好吧, i是一个参数,但从未求值
constexpr int y = blorg(false,i); //没办法

对于 std :: integral_constant 成员函数,可以考虑 * this ,例如 blorg i $ c>函数-如果执行未取消引用它,则可以在不作为编译时常量的情况下将其传递。


Although I've used code like this before, and it's clear that the compiler has enough information to work, I don't really understand why this compiles:

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;
}

Live example: http://coliru.stacked-crooked.com/a/fc9cc6b954912bc5.

Seems to work with both gcc and clang. The thing is that while integral_constant has a constexpr conversion to the stored integer, constexpr member functions implicitly take the object itself as an argument, and therefore such a function cannot be used in a constexpr context unless the object we're calling the member function itself can be treated as constexpr.

Here, i is an argument passed to foo, and therefore i most certainly cannot be treated as constexpr. Yet, it is. An even simpler example:

template <class I>
void foo(I i) {
    constexpr std::size_t j = i;
}

This compiles too, as long as std::integral_constant<std::size_t, 0>{} is passed to foo.

I feel like I'm missing something obvious about the constexpr rules. Is there an exception for stateless types, or something else? (or, maybe, a compiler bug in two major compilers? This code seems to work on clang 5 and gcc 7.2).

Edit: an answer has been posted, but I don't think it's quite sufficient. In particular, given the last definition of foo, why does:

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

Compile, but not:

foo(0);

Both 0 and std::integral_constant<std::size_t, 0>{} are constant expressions.

Edit 2: It seems like it boils down to the fact that calling a constexpr member function even on an object that is not a constant expression, can itself be regarded as a constant expression, as long as this is unused. This is being taken as obvious. I don't consider this obvious:

constexpr int foo(int x, int y) { return x; }

constexpr void bar(int y) { constexpr auto x = foo(0, y); }

This does not compile, because y as passed into foo is not a constant expression. It's being unused does not matter. Therefore, a complete answer needs to show some kind of language from the standard to justify the fact that a constexpr member function can be used as a constant expression, even on a non-constant expression object, as long as this is unused.

解决方案

The rules for compile time constant expressions changed with constexpr, A LOT, but they aren't new. Before constexpr, there were already compile time constant expressions... and the old rules are preserved as special cases in the new specification, to avoid breaking huge amounts of existing code.

For the most part, the old rules dealt with compile-time constants of integral type aka integral constant expression... which is exactly the situation you're dealing with. So no, there isn't anything weird in the constexpr rules... it's the other older rules kicking in that have nothing to do with constexpr.

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

...

  • an lvalue-to-rvalue conversion unless it is applied to
    • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
    • a non-volatile glvalue that refers to a subobject of a string literal, or
    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

You're right that the third subbullet does not apply. But the first one does.

So you have an interesting interplay between new rules which allow function returns to be compile-time constant, depending on the rules for evaluation on the abstract machine, and the legacy behavior allowing integral values to be compile-time constant even if not marked as such.


Here's a quick example why it doesn't matter that this is an implicit argument. Being an argument doesn't mean an object is evaluated:

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

For std::integral_constant member functions, you can consider *this like i in the blorg function -- if execution doesn't dereference it, it's ok for it to be passed around without being a compile-time constant.

这篇关于通过值传递的积分常量,视为constexpr?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆