C++ - 我能创建编译时变量对象吗?

5
最近我使用了constexpr,但我意识到我使用它的方式是错误的。我想知道是否可以创建一个编译时变量(或变量对象)。来自cppreference.com的constexpr定义告诉我们:
constexpr说明符声明函数或变量的值可以在编译时评估。
那么为什么以下代码是不正确的?
#include <iostream>

int main()
{
    constexpr int x = 30;
    x += 10;
    std::cout << x;
}

这个整数可以在编译时完美地评估。我知道编译器可以优化这样的变量而不需要constexpr修饰符,但如果我想要一个编译时对象呢?

#include <iostream>

class ctFoo {
public:
    ctFoo()
        : value{ 0 }
    {
    }
    int accumulate(int value_) {
        return (value += value_), value;
    }
    int value;
};

int main()
{
    ctFoo foo;
    std::cout << foo.accumulate(100);
}

我想知道这段代码将在编译时被评估的确定性有多大? 我问这个问题是因为我正在编写一些Vector2和Vector3数学代码,并且我希望创建这样的实现,可以处理编译时和运行时计算。这是否可能?
谢谢。

编辑

正如max66指出的那样,constexpr意味着const,但我想问的是:为什么会这样?现代编译器应该能够在编译时推导出它的值。 此外,我知道我可以简单地创建另一个constexpr常量(例如顶部代码示例),但我的问题涉及更复杂的代码。


你忘记在 constexpr 中加入 const 了。 - max66
是的,使用constexpr隐含了constness(自C++14起不再在声明方法时),但我想问一下-为什么这样?C++编译器应该能够推断出它的值,因为其在编译时已知。 - Poeta Kodu
自C++20以来,有constinit - Tellegar
2个回答

4

So why is following code incorrect?

#include <iostream>

int main()
{
    constexpr int x = 30;
    x += 10;
    std::cout << x;
}

constexpr 意味着 const。您需要将其限制在 constexpr 上下文中:

constexpr int foo() {
    int x = 30;
    x += 10;
    return x;
}

But what if I want to have a compile-time object?

#include <iostream>

class ctFoo {
public:
    ctFoo()
        : value{ 0 }
    {
    }
    int accumulate(int value_) {
        return (value += value_), value;
    }
    int value;
};

为其提供constexpr支持:

constexpr ctFoo() : value{ 0 }

constexpr int accumulate(int value_) {
    value += value_;
    return value;
}

你现在拥有的保证是,如果你的ctFoo对象是一个常量表达式,并且你在像foo函数示例中的constexpr上下文中调用accumulate,那么你可以在编译时使用结果。例如:
constexpr int foo() {
    ctFoo f;
    f.accumulate(10);
    return f.value;
}

static_assert(foo() == 10);

或者:

constexpr void accumulate(ctFoo& f) {
    f.accumulate(10);
}

constexpr int foo() {
    ctFoo f;
    accumulate(f);
    return f.value;
}

static_assert(foo() == 10);

重要的是要记住,运行时评估也是一种选择。如果我将一些ctFoovalue设置为运行时值(例如用户输入),那么编译时不可能发生accumulate调用。但没关系 - 相同的代码在两个上下文中都可以使用。

如果在创建constexpr对象时使用constexpr int accumulate(int value_{}),则无法修改内部值,因为该方法也应该是“const”,但它不能这样做,因为它会修改内部值... - Daniel Illescas
@DanielIllescas,没错,你不能在一个const对象上调用accumulate,包括constexpr变量。我在我的使用示例中没有使用它们。然而,自从C++14以来,允许一个constexpr成员函数是非const的,以便在纯函数的实现中非纯度是有用的。例如,可以更容易地使用循环累加值,而不需要使用递归,但仍然可以确保整个函数是纯的。 - chris
@chris 好的,我明白了,我必须使用constexpr函数来实现,但是为什么呢? 这段代码也有意义吧? int main() { constexpr ctFoo f; f.accumulate(10); } - Poeta Kodu
@razzorflame,这有可能是这样的。当使用constexpr在编译时,语言选择使其清晰明了程序的某些部分是纯净的。无论是否在内部使用非纯技术,constexpr函数在编译时的评估都将是纯净的,否则将会出现编译器错误。运行时是另一回事。也许决定在编译时评估期间允许这种非纯度逃逸到这样的上下文中是对语言有害的。(放松的)constexpr提案或围绕它的讨论可能会给出一些动机。 - chris
好的。是否可以在主函数内使用lambda创建这样的编译时上下文,并将其分配给constexpr常量?例如:constexpr ctFoo f = ( [] { ctFoo foo; foo.accumulate(10); return foo; })(); - Poeta Kodu
显示剩余2条评论

1

你需要继续阅读:

在对象声明中使用的constexpr指示符意味着const

你不能更改constexpr,这就是你能够在常量表达式中使用它的全部意义。

接下来需要考虑的是,你似乎混淆了两个不同的上下文。我毫不怀疑编译器将能够完全优化掉你最后一个示例,但那是constexpr所做的事情之外的东西。该关键字表示其所表示的内容可以在编译时计算,但不一定要在编译时计算。因此,你可以编写一个constexpr函数,在其中的所有内容都可能在编译时计算,并且仍然可以在运行时工作。

唯一真正拥有可以在编译时计算(因此没有优化器)并且可以修改的对象的时间是当你处于编译时上下文中时。例如,在constexpr函数中,你就处于这样的上下文中:

constexpr int foo() {
    int a = 10;
    ++a; // I can modify a.
    return a;
    // even if I did this: int Array[foo()];
}

但是在普通函数中,你没有这样的能力,语言不允许这样做。
所以,回答你的问题:
“我有什么保证,这段代码会在编译时被评估?”
你没有任何保证,因为你没有使用constexpr。即使你使用了constexpr函数,如果你在某个不需要编译时评估的地方调用了该函数,该函数可能会在运行时被调用。
“这种情况是否可能发生?”
当然可能,正如前面所说,你可以创建一个constexpr函数,如果需要,它将在编译时被评估。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接