常量参数"real"常量吗?

Are const arguments "real" constants?

本文关键字:常量 real 参数      更新时间:2023-10-16

AFAIK 从常量变量中删除常量是未定义的行为:

const int i = 13;
const_cast<int&>(i) = 42;      //UB
std::cout << i << std::endl;   //out: 13

但是常量函数参数是"实"常量吗?让我们考虑以下示例:

void foo(const int k){
const_cast<int&>(k) = 42;    //UB?
std::cout << k << std::endl;
}
int main(){
foo(13); //out: 42
}

似乎编译器不会对const int k应用与const int i相同的优化。

第二个例子中有UB吗?const int k帮助编译器优化代码还是编译器只检查常量正确性,仅此而已?

const int i = 13;中的i可以用作常量表达式(作为模板参数或案例标签(,并且尝试修改它是未定义的行为。如此是为了向后兼容没有constexpr的 C++11 之前的代码。

声明void foo(const int k);void foo(int k);声明相同的函数;参数的顶级const不参与函数的签名。参数k必须按值传递,因此不能是"真实"常量。抛弃它的恒定性并不是未定义的行为。编辑:但是任何修改它的尝试仍然没有定义,因为它是 const 对象 [basic.type.qualifier] (1.1(:

const 对象是 const T 类型的对象或此类对象的不可变子对象。

通过 [dcl.type.cv] 4 个常量对象无法修改:

除了可以修改任何声明为 mutable (10.1.1( 的类成员之外,任何在其生存期 (6.8( 期间修改 const 对象的尝试都会导致未定义的行为。

由于函数参数具有自动存储持续时间,因此 [basic.life] 10 也无法创建其存储中的新对象:

在具有静态、线程或自动存储持续时间的 const 完整对象所占用的存储中创建新对象,或者在此类 const 对象在其生存期结束之前用于占用的存储中创建新对象,会导致未定义的行为。

目前还不清楚,如果有计划抛弃它的恒常性,为什么k首先被宣布为const?它的唯一目的就是混淆和混淆。

一般来说,我们应该在任何地方都支持不变性,因为它可以帮助人们推理。它还可以帮助编译器进行优化。然而,当我们只声明不变性而不尊重它时,它的作用恰恰相反并令人困惑。

我们应该支持的其他事情是纯函数。这些不依赖于或修改任何外部状态,也没有副作用。这些也更容易推理和优化人员和编译器。 目前,此类函数可以声明为constexpr。但是,将按值参数声明为const对我的知识没有任何帮助,即使在constexpr函数的上下文中也是如此。

但是常量函数参数是"实"常数吗?

我认为答案是肯定的。

例如,它们不会存储在 ROM 中,因此丢弃 const 至少看起来可以正常工作。但我对标准的解读是参数的类型是const int,因此它是一个const 对象([basic.type.qualifier](,因此修改它是未定义的([dcl.type.cv](。

您可以相当轻松地确认参数的类型:

static_assert( std::is_same_v<const int, decltype(k)> );

第二个例子中有UB吗?

是的,我认为有。

const int k 是否帮助编译器优化代码,或者编译器只是检查 const 正确性,仅此而已?

理论上,编译器可以假设在下面的示例中k不会通过调用g(const int&)来更改,因为修改k将是 UB:

extern void g(const int&);
void f(const int k)
{
g(k);
return k;
}

但在实践中,我认为编译器不会利用这一点,而是假设k可能会被修改(编译器资源管理器演示(。

我不确定你所说的"真正的"常量是什么意思,但在这里。

您的const int i是任何函数之外的变量声明。 由于修改该变量会导致未定义行为,因此编译器可以假设其值永远不会更改。 一个简单的优化是,无论你从i读取的任何地方,编译器都不必去从主内存中读取值,它可以发出程序集以直接使用该值。

您的const int k(或更有趣的const int & k(是一个函数参数。 它所承诺的只是这个函数不会改变k的值。 这并不意味着它不能在其他地方更改。 函数的每次调用都可以具有不同的k值。