c++ 如何声明constexpr extern?

ftf50wuq  于 2023-06-25  发布在  其他
关注(0)|答案(7)|浏览(123)

是否可以声明一个变量extern constexpr并在另一个文件中定义它?
我试过了,但编译器给出错误:
constexpr变量“i”的声明不是定义
in .h:

extern constexpr int i;

in .cpp:

constexpr int i = 10;
pinkon5k

pinkon5k1#

不你不能这么做以下是标准的规定(第7.1.5节):
1 constexpr说明符只能应用于变量或变量模板的定义、函数或函数模板的声明或文字类型的静态数据成员的声明(3.9)。如果函数、函数模板或变量模板的任何声明都有constexpr说明符,那么它的所有声明都应该包含constexpr说明符。[注意:显式专门化可能与constexpr说明符方面的模板声明不同。函数参数不能声明为constexpr。- 尾注]
标准给出的一些示例:

constexpr void square(int &x);  // OK: declaration
constexpr int bufsz = 1024;  // OK: definition
constexpr struct pixel {  // error: pixel is a type
    int x;
    int y;
    constexpr pixel(int);  // OK: declaration
};

extern constexpr int memsz; // error: not a definition
mftmpeh8

mftmpeh82#

C++17 inline变量

这个令人敬畏的C++17特性允许我们:

  • 对于每个常量,只需使用单个存储器地址即可
  • 存储为constexpr
  • 从一个标题的单行中完成

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream
C++标准保证地址是相同的。C++17 N4659 standard draft 10.1.6“内联说明符”:
6具有外部链接的内联函数或变量在所有转换单元中应具有相同的地址。
cppreference https://en.cppreference.com/w/cpp/language/inline解释说,如果没有给出static,则它具有外部链接。
标签:How do inline variables work?
在GCC 7.4.0,Ubuntu 18.04中测试。

6yoyoihd

6yoyoihd3#

您可能需要的是extern和constexpr初始化,例如:

// in header
extern const int g_n;

// in cpp
constexpr int g_n = 2;

这是在Visual Studio 2017中仅通过一致性模式支持:

zf2sa74q

zf2sa74q4#

不外部constexpr没有任何意义。请阅读http://en.cppreference.com/w/cpp/language/constexpr
即钻头
必须立即构造它或为其赋值。

ejk8hzay

ejk8hzay5#

我同意上面的“swang”,但有一个后果。考虑:
ExternHeader.hpp

extern int e; // Must be extern and defined in .cpp otherwise it is a duplicate symbol.

ExternHeader.cpp

#include "ExternHeader.hpp"
int e = 0;

ConstexprHeader.hpp

int constexpr c = 0; // Must be defined in header since constexpr must be initialized.

Include1.hpp

void print1();

Include1.cpp

#include "Include1.hpp"
#include "ExternHeader.hpp"
#include "ConstexprHeader.hpp"
#include <iostream>

void print1() {
    std::cout << "1: extern = " << &e << ", constexpr = " << &c << "\n";
}

Include2.hpp

void print2();

Include2.cpp

#include "Include2.hpp"
#include "ExternHeader.hpp"
#include "ConstexprHeader.hpp"
#include <iostream>

void print2() {
    std::cout << "2: extern = " << &e << ", constexpr = " << &c << "\n";
}

main.cpp

#include <iostream>
#include "Include1.hpp"
#include "Include2.hpp"

int main(int argc, const char * argv[]) {
    print1();
    print2();
    return 0;
}

哪些打印:

1: extern = 0x1000020a8, constexpr = 0x100001ed0
2: extern = 0x1000020a8, constexpr = 0x100001ed4

在IE中,constexpr被分配两次,而extern被分配一次。这对我来说是违反直觉的,因为我“期望”constexprextern更优化。
编辑:constconstexpr在分配方面具有相同的行为,因此从这个Angular 来看,行为符合预期。但是,正如我所说,当我遇到constexpr的行为时,我感到惊讶。

j9per5c4

j9per5c46#

是的有点...

//===================================================================
// afile.h

#ifndef AFILE
#define AFILE

#include <cstddef>
#include <iostream>

enum class IDs {

  id1,
  id2,
  id3,
  END

};

// This is the extern declaration of a **constexpr**, use simply **const**
extern const int ids[std::size_t(IDs::END)];

// These functions will demonstrate its usage

template<int id> void Foo() { std::cout << "I am " << id << std::endl; }

extern void Bar();

#endif // AFILE

//===================================================================
// afile.cpp

#include "afile.h"

// Here we define the consexpr. 
// It is **constexpr** in this unit and **const** in all other units
constexpr int ids[std::size_t(IDs::END)] = {

  int(IDs::id1),
  int(IDs::id2),
  int(IDs::id3)

};

// The Bar function demonstrates that ids is really constexpr
void Bar() {

  Foo<ids[0]      >();
  Foo<ids[1] + 123>();
  Foo<ids[2] / 2  >();

}

//===================================================================
// bfile.h

#ifndef BFILE
#define BFILE

// These functions will demonstrate usage of constexpr ids in an extern unit

extern void Baz();
extern void Qux();

#endif // BFILE

//===================================================================
// bfile.cpp

#include "afile.h"

// Baz demonstrates that ids is (or works as) an extern field
void Baz() {

  for (int i: ids) std::cout << i << ", ";
  std::cout << std::endl;

}

// Qux demonstrates that extern ids cannot work as constexpr, though
void Qux() {

#if 0 // changing me to non-0 gives you a compile-time error...

  Foo<ids[0]>();

#endif

  std::cout << "Qux: 'I don't see ids as consexpr, indeed.'" 
            << std::endl;

}

//===================================================================
// main.cpp

#include "afile.h"
#include "bfile.h"

int main(int , char **)
{

  Bar();
  Baz();
  Qux();

  return 0;
}
ckocjqey

ckocjqey7#

你可以做到这一点,你只需要在头文件中将constexpr替换为const

// declaration, possibly in header
extern const int i;

// source file
constexpr int i = 0;

constexpr对象背后的基本思想是:

  • const
  • 用常量表达式初始化它

前一部分可以通过在头文件中使用const来完成,后一部分只与源文件中的初始化相关。

真的可以吗?!

是的。让我们看看标准中的相关章节:
两个实体的声明声明同一个实体,如果,[...],它们对应,具有相同的目标作用域(不是函数或模板参数作用域),并且

  • 它们出现在同一个翻译单元中,或者[...]
      • 它们都声明了带有外部链接的名称**
  • [www.example.com] § 8 basic.link] §8
    简单地说,两个i具有相同的名称,因此它们是对应的,并且由于extern,它们都具有外部链接。
    对于实体E的任何两个声明:
  • 如果一个声明E为变量或函数,另一个声明E为相同类型。
  • [...]
  • [www.example.com] § 11 basic.link] §11
    这就引出了问题如果两个变量中的一个是constexpr,另一个是const,那么这两个变量是同一类型吗?
    在对象声明中使用的constexpr说明符将对象声明为const。[...]
  • [dcl. constexpr] § 6
    答案是肯定的,它只是使我们的对象const,它不会以任何其他方式改变类型。唯一剩下的问题是,是否允许我们将constexpr放在一个声明上,而不是另一个声明上:
    如果函数或函数模板的任何声明具有constexprconsteval说明符,则其所有声明都应包含相同的说明符。
  • [dcl. constexpr] § 1
    不,只有函数的限制,没有变量的限制。允许声明一个const和另一个constexpr

这是允许的,但这不是毫无用处吗?

不完全是。一个可能的用例是,如果你有一个很大的constexpr查找表,你想在编译时计算,但不想把这个初始化放在头文件中,以减少编译时间。如果在编译时计算这个表真的很重要,但在任何地方都可以看到它的内容定义并不那么重要(对于内联和优化),extern constexpr可以提供帮助。

  • inline constexpr将要求在任何地方都有相同的定义(和初始化)以不违反一个定义规则。因此,我们在包含头文件的每个翻译单元中都会产生成本。
  • static constexpr甚至更糟,因为每个翻译单元都有它自己的这个大查找表的副本。
  • extern constexpr完美地涵盖了这个用例。
  • 注意:并非所有编译器默认都符合标准。使用MSVC编译时使用/Zc:externConstexpr。*

相关问题