静态 constexpr 数据成员已初始化为 off-class,以及可能的解决方法?

Static constexpr data member initialized off-class, and a possible workaround?

I know that it's not possible in C++17 to declare a static constexpr data member without its immediate initialization (although yet elsewhere 他们使用这样的例子)。

// --- in header ---
struct Data{
    LPCTSTR str;
    int i;
};

class C{
public:
    static constexpr Data MyData; // VS complains: this requires an in-class initializer
};

// --- in implementation file ---
constexpr Data C::MyData={
    _T("Hi"), 12345
};

我的代码暂时还必须在较旧的 Visual Studio 中编译,其中缺少 constexpr 概念很容易被#defined as

#if _MSC_VER<=1600 // pre-C++11 compiler?
    #define constexpr const
#endif

突然之间,代码库将与从 VS6 到 VS2019 的几乎任何东西兼容。如果通过 Visual Studio.

中的某些隐藏命令行开关允许 off-class 初始化,请纠正我

所以我想出了一个我不确定其有效性的解决方法。与数据成员不同,静态 constexpr 方法不必具有 in-class 主体。所以我这样做:

// --- in header ---
struct Data{
    LPCTSTR str;
    int i;
};

class C{
public:
    static constexpr Data GetMyData();
};

// --- in implementation file ---
constexpr Data C::GetMyData(){
    constexpr Data D={
        _T("Hi"), 12345
    };
    return D;
}

然后我可以从 constexpr 中受益,但仍然可以在旧的 Visual Studios:

下编译(借助上面的 #define)
constexpr LPCTSTR Greeting=C::GetMyData().str;

并且还出人意料地在 运行 时获取返回对象的地址:

const Data *p=&C::GetMyData();
const LPCTSTR greeting=p->str;

这在我的应用程序中非常棒并且值得期待!但是让我担心我收到的什么地址?它是编译到生成的可执行文件中的对象的地址(并且在 运行 时驻留在受保护的内存中)还是在 运行 时真正创建的全局变量的地址?我当然希望在较新的 Visual Studios 下编译时更早发生,而我不关心 C++11 之前版本的情况。

提前致谢。

重点是constexpr并不是说“只在编译时存在”,而是“可以在编译时使用。所以只要由于您不以 要求 值在编译时可访问的方式访问 constexpr 变量,因此可以将其视为 const.

事实上,对于您发布的代码,您看到 constexpr 好处的唯一地方是在实现文件中。在其他任何地方,它别无选择,只能“降级”为常规 const,因为在编译期间解析它的值是不可能的。

我认为你的大部分问题都可以通过将代码解构为等效示例来解决:

// MyClass.h
class MyClass {
  // static constexpr Data MyData;
  static const Data MyData;
};
// MyClass.cpp
namespace {
  constexpr Data MyDataValue = ...;
}

const Data MyData::MyData = MyDataValue;

// If you use MyData inside of the implementation file, it gets the conxtepr value.
void MyClass:foo() {
  // std::array<float, MyClass::Data.some_member> some_array; 
  std::array<float, MyDataValue.some_member> some_array; // no problem
}
// Some_other_file.cpp

int foo() {
  // That's fine, MyClass::Data will be resolved either at runtime or during lto.
  return MyClass::Data.some_member;
}

int bar() {
  std::array<float, MyClass::Data.some_member> some_array; // ERROR! Can't be resolved at compile time
}

那么能够在不立即初始化的情况下声明静态 constexpr 数据成员有什么意义呢?

只要你在实际使用之前在header中定义它,那么一切都很好,有时可以派上用场。但是,在您的情况下,由于您想在实现文件中定义变量,因此您创建了这个两层系统,其中成员在实现文件中为 constexpr,在其他任何地方为 const

这就是说:如果所有延迟的 constexpr 定义都在实现文件中,就像在您发布的代码中一样,那么只需将它们设为 const,并在本地使用 constexpr实现文件中的匿名命名空间(如果需要的话)。根本不需要宏。