常量表达式聚合类型和类

constexpr 函数中,我们介绍了 constexpr 函数,这些函数可以在编译时或运行时进行求值。例如:

#include <iostream>

constexpr int greater(int x, int y)
{
    return (x > y ? x : y);
}

int main()
{
    std::cout << greater(5, 6) << '\n'; // greater(5, 6) 可能在编译时或运行时求值

    constexpr int g { greater(5, 6) };  // greater(5, 6) 必须在编译时求值
    std::cout << g << '\n';             // 输出 6

    return 0;
}

在这个例子中,greater() 是一个 constexpr 函数,而 greater(5, 6) 是一个常量表达式,它可以在编译时或运行时求值。因为 std::cout << greater(5, 6) 在非 constexpr 上下文中调用了 greater(5, 6),所以编译器可以选择在编译时或运行时对 greater(5, 6) 进行求值。当 greater(5, 6) 用于初始化 constexpr 变量 g 时,greater(5, 6)constexpr 上下文中被调用,因此必须在编译时求值。

现在考虑以下类似的例子:

#include <iostream>

struct Pair
{
    int m_x {};
    int m_y {};

    int greater() const
    {
        return (m_x > m_y ? m_x : m_y);
    }
};

int main()
{
    Pair p { 5, 6 };                  // 输入值是 `constexpr` 值
    std::cout << p.greater() << '\n'; // p.greater() 在运行时求值

    constexpr int g { p.greater() };  // 编译错误:greater() 不是 `constexpr`
    std::cout << g << '\n';

    return 0;
}

在这个版本中,我们有一个名为 Pair 的聚合结构体,而 greater() 现在是一个成员函数。然而,由于成员函数 greater() 不是 constexprp.greater() 不是一个常量表达式。当 std::cout << p.greater() 在非 constexpr 上下文中调用 p.greater() 时,p.greater() 将在运行时求值。然而,当我们尝试使用 p.greater() 来初始化 constexpr 变量 g 时,我们得到了一个编译错误,因为 p.greater() 不能在编译时求值。

由于 p 的输入值是 constexpr 值(5 和 6),似乎 p.greater() 应该能够在编译时求值。但我们该如何做到这一点呢?

常量表达式成员函数

与非成员函数一样,成员函数也可以通过使用 constexpr 关键字来声明为常量表达式函数。常量表达式成员函数可以在编译时或运行时求值。

#include <iostream>

struct Pair
{
    int m_x {};
    int m_y {};

    constexpr int greater() const // 可在编译时或运行时求值
    {
        return (m_x > m_y ? m_x : m_y);
    }
};

int main()
{
    Pair p { 5, 6 };
    std::cout << p.greater() << '\n'; // 没问题:p.greater() 在运行时求值

    constexpr int g { p.greater() };  // 编译错误:p 不是 `constexpr`
    std::cout << g << '\n';

    return 0;
}

在这个例子中,我们将 greater() 声明为 constexpr 函数,因此编译器可以在运行时或编译时对其进行求值。

当我们在运行时表达式 std::cout << p.greater() 中调用 p.greater() 时,它在运行时求值。

然而,当 p.greater() 用于初始化 constexpr 变量 g 时,我们得到了一个编译错误。尽管 greater() 现在是 constexpr,但 p 仍然不是 constexpr,因此 p.greater() 不是一个常量表达式。

常量表达式聚合类型

好吧,既然我们需要 pconstexpr,那么我们干脆把它声明为 constexpr

#include <iostream>

struct Pair // Pair 是一个聚合类型
{
    int m_x {};
    int m_y {};

    constexpr int greater() const
    {
        return (m_x > m_y ? m_x : m_y);
    }
};

int main()
{
    constexpr Pair p { 5, 6 };        // 现在是 `constexpr`
    std::cout << p.greater() << '\n'; // p.greater() 在运行时或编译时求值

    constexpr int g { p.greater() };  // p.greater() 必须在编译时求值
    std::cout << g << '\n';

    return 0;
}

由于 Pair 是一个聚合类型,而聚合类型隐式支持 constexpr,因此我们完成了。这可以正常工作!由于 p 是一个 constexpr 类型,且 greater() 是一个 constexpr 成员函数,因此 p.greater() 是一个常量表达式,可以在只允许常量表达式的地方使用。

相关内容

我们在第 13.8 课——结构体聚合初始化中介绍了聚合类型。

常量表达式类对象和常量表达式构造函数

现在让我们把 Pair 变成一个非聚合类型:

#include <iostream>

class Pair // Pair 不再是聚合类型
{
private:
    int m_x {};
    int m_y {};

public:
    Pair(int x, int y): m_x { x }, m_y { y } {}

    constexpr int greater() const
    {
        return (m_x > m_y ? m_x : m_y);
    }
};

int main()
{
    constexpr Pair p { 5, 6 };       // 编译错误:p 不是字面量类型
    std::cout << p.greater() << '\n';

    constexpr int g { p.greater() };
    std::cout << g << '\n';

    return 0;
}

这个例子几乎和前面的例子一模一样,只是 Pair 不再是聚合类型(因为它有私有数据成员和构造函数)。

当我们编译这个程序时,我们得到了一个关于 Pair 不是“字面量类型”的编译错误。这是什么情况?

在 C++ 中,字面量类型是任何可能在常量表达式中创建对象的类型。换句话说,除非类型符合字面量类型,否则对象不能是 constexpr。而我们的非聚合 Pair 不符合字面量类型。

术语

字面量和字面量类型是不同的(但相关)概念。字面量是一个插入到源代码中的 constexpr 值。字面量类型是可以用作 constexpr 值类型的类型。字面量总是具有字面量类型。然而,具有字面量类型的值或对象不必是字面量。

字面量类型的定义较为复杂,可以在 cppreference 上找到总结。但值得一提的是,字面量类型包括:

  • 标量类型(那些持有单个值的类型,例如基本类型和指针)
  • 引用类型
  • 大多数聚合类型
  • 具有 constexpr 构造函数的类

现在我们明白了为什么我们的 Pair 不是字面量类型。当类对象被实例化时,编译器会调用构造函数来初始化对象。而我们 Pair 类中的构造函数不是 constexpr,因此它不能在编译时被调用。因此,Pair 对象不能是 constexpr

解决方法很简单:我们只需将构造函数也声明为 constexpr

#include <iostream>

class Pair
{
private:
    int m_x {};
    int m_y {};

public:
    constexpr Pair(int x, int y): m_x { x }, m_y { y } {} // 现在是 `constexpr`

    constexpr int greater() const
    {
        return (m_x > m_y ? m_x : m_y);
    }
};

int main()
{
    constexpr Pair p { 5, 6 };
    std::cout << p.greater() << '\n';

    constexpr int g { p.greater() };
    std::cout << g << '\n';

    return 0;
}

这按预期工作,就像我们聚合版本的 Pair 一样。

最佳实践

如果你希望你的

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"