通过值传递的积分常量,视为constexpr? [英] Integral constant passed by value, treated as 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 $ c中的
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屋!