constexpr是否意味着内联?

149

考虑以下内联函数:

// Inline specifier version
#include<iostream>
#include<cstdlib>

inline int f(const int x);

inline int f(const int x)
{
    return 2*x;
}

int main(int argc, char* argv[])
{
    return f(std::atoi(argv[1]));
}

以及等效的constexpr版本:

// Constexpr specifier version
#include<iostream>
#include<cstdlib>

constexpr int f(const int x);

constexpr int f(const int x)
{
    return 2*x;
}

int main(int argc, char* argv[])
{
    return f(std::atoi(argv[1]));
}

我的问题是:在非常量参数传递给constexpr函数时,constexpr关键字是否意味着inline关键字,即编译器是否会尝试内联函数,就好像在其声明中放置了inline关键字一样?

C++11标准是否保证?


9
“编译器是否会尝试内联该函数?”并不是 inline 关键字的作用。 (或者也许我误解了您的措辞。) - Luc Danton
10
“inline”关键字不再与“内联”有任何关系。 - K-ballo
4
这个问题基于错误的假设,即“inline”与内联直接相关。因此,就那个意义而言,“constexpr”并不意味着“inline”修饰符,因为这种意义不存在。 - Christian Rau
2个回答

176

是的([dcl.constexpr], C++11标准第7.1.5/2节): "constexpr函数和constexpr构造函数隐式地被视为内联(7.1.2)。"

需要注意的是,inline修饰符对于编译器是否会将函数展开成内联形式实际上影响非常小(如果有的话)。不过,它会影响到唯一定义规则,并且从这个角度来看,编译器需要遵循与inline函数相同的规则来处理constexpr函数。

此外,我还要补充一点,即使constexpr意味着inline,在C++11中,constexpr函数的规则要求其足够简单,因此通常是内联展开的好候选对象(主要的例外是递归函数)。然而,自那以后,规则已经变得越来越宽松,因此constexpr可以应用于更大、更复杂的函数。


鉴于常量表达式的想法是在编译时进行评估,我认为大多数使用constexpr函数的情况根本不会导致任何代码生成... - Kerrek SB
14
constexpr函数潜在地可以在编译时进行评估。然而,C++14标准中有许多这样的函数很可能会在运行时被调用。例如:std::array<T,N>::at - Eponymous
1
@Eponymous 是的,但只有最简化的形式会保留为操作码。例如:边界检查将在构建时进行评估,因为它们的代码路径是常量。但返回的值将是 *(data+offset)。 - v.oddou

68

constexpr在非静态变量中并不意味着inline(C++17中的内联变量)

constexpr在函数中意味着inline,但是对于非静态变量却没有这种影响,尤其考虑到C++17中的内联变量。

例如,如果您采用我在 如何使用内联变量? 中发布的最小示例,并删除inline,只剩下constexpr,然后该变量会获得多个地址,这也是内联变量避免的主要问题。

然而,constexpr静态变量隐式具有内联效果。

constexpr函数的最小示例

正如在https://dev59.com/HmYq5IYBdhLWcg3wjBQM#14391320中提到的那样,inline的主要作用不是内联函数,而是允许对函数进行多次定义, 标准引用: 如何在C++头文件中包含实现?

我们可以通过操作以下示例来观察到这一点:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    assert(shared_func() == notmain_func());
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline int shared_func() { return 42; }
int notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

int notmain_func() {
    return shared_func();
}

编译并运行:

g++ -c -ggdb3  -O0 -Wall -Wextra -std=c++11 -pedantic-errors  -o 'notmain.o' 'notmain.cpp' 
g++ -c -ggdb3  -O0 -Wall -Wextra -std=c++11 -pedantic-errors  -o 'main.o' 'main.cpp' 
g++ -ggdb3  -O0 -Wall -Wextra -std=c++11 -pedantic-errors  -o 'main.out' notmain.o main.o
./main.out

如果我们从shared_func中删除inline,链接将会失败:

multiple definition of `shared_func()'

由于头文件被包含在多个.cpp文件中,因此出现这种情况。

但是如果我们用constexpr替换inline,那么它再次起作用,因为constexpr也意味着inline

GCC通过在ELF目标文件上将符号标记为弱来实现这一点:How can a C++ header file include implementation?

在GCC 8.3.0中进行了测试。


10
顺便说一下,声明为 constexpr 的静态类成员变量仍然是内联的。根据 cppreference.com ,声明为 constexpr 的静态成员变量(但不包括命名空间作用域的变量)隐式地成为内联变量。 - anton_rh
@anton_rh 谢谢,我之前没有看到那个规则,已经更新了答案。 - Ciro Santilli OurBigBook.com
2
如果您有机会,能否更正一下您的说法“constexpr静态变量实际上是隐式静态的”?我猜其中一个单词“static”应该是“inline”。 - jorgbrown
一个简单的问题:#ifndef - #define 这种方式是否比 #pragma once 更推荐使用? - Sohail Si
1
@SohailSi 请参考:https://dev59.com/gXM_5IYBdhLWcg3w-4dg - Ciro Santilli OurBigBook.com
显示剩余2条评论

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