为什么非静态数据成员不能是constexpr?

58

这是有效的代码:

struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  const int xVal { 0 };
  const int yVal { 0 };
};

但我真的希望在这里声明xValyValconstexpr--像这样:

struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  constexpr int xVal { 0 };         // error!
  constexpr int yVal { 0 };         // error!
};

正如所示,代码无法编译。原因是(根据7.1.5/1),只有静态数据成员可以声明为constexpr。但是为什么呢?


23
因为这些是在运行时初始化的成员变量,只有在其被初始化之后才能进行求值。 - Captain Obvlious
@remyabel 我相信我无法使用constexpr的原因不同于我没有使用花括号初始化时为什么会出现constexpr错误的原因... - IdeaHat
@IdeaHat,你看过答案了吗? - user3920237
@remyabel 是的。它说你必须使用大括号初始化器,就像 KnowItAllWannabe 所做的那样。 - IdeaHat
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - KnowItAllWannabe
1
@KnowItAllWannabe,我将其标记为不具有建设性,评论清空器最终会吹走所有的评论。 - IdeaHat
1个回答

57

想一想constexpr是什么意思。它的意思是我可以在编译时解析这个值。

因此,类的成员变量本身不能是constexpr...xVal所属的实例直到实例化时才存在!拥有xVal的东西可能是constexpr,这将使xVal成为constexpr,但xVal本身永远不可能是constexpr

这并不意味着这些值不能是常量表达式...实际上,类的constexpr实例可以使用这些变量作为常量表达式:

struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  int xVal { 0 };
  int yVal { 0 };
};

constexpr S s;

template <int f>//requires a constexpr
int foo() {return f;}

int main()
{
   cout << "Hello World" << foo<s.xVal>( )<< endl; 

   return 0;
}

编辑:下面有很多关于本文中存在一些隐含问题的讨论。

"为什么不能通过声明其成员为constexpr,强制所有类的实例都是constexpr的?"

看下面的例子:

//a.h
struct S;
struct A {std::unique_ptr<S> x; void Foo(); A();/*assume A() tries to instantiate an x*/}

//main.cpp

int main(int argc, char** argv) {
  A a;
  a->foo();
}


//S.h
struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  constexpr int xVal { 0 };         // error!
  constexpr int yVal { 0 };
};

A和S的定义可能在完全不同的编译单元中,因此如果忘记了A的实现,那么S必须是constexpr的事实直到链接时才能知道。这种模棱两可的情况很难调试和实现。更糟糕的是,S的接口可能完全暴露在共享库、COM接口等中...这可能会完全改变共享库的所有基础设施,这可能是不可接受的。

另一个原因是这种影响力有多具有传染性。如果类的任何成员都是constexpr的,则所有成员(以及它们的所有成员)和所有实例都必须是constexpr的。看下面的场景:

//S.h
struct S {
  constexpr S(int x, int y): xVal(x), yVal(y) {}
  constexpr S(int x): xVal(x) {}
  constexpr S() {}

  constexpr int xVal { 0 };         // error!
  int yVal { 0 };
};
任何S的实例都必须是constexpr才能保持一个排他性constexpr的xval。因为xVal是constexpr,所以yVal本质上也变成了constexpr。在技术上没有编译器原因阻止你这样做(我不认为有),但它不感觉很像C++。
“好吧,但我真的想使类的所有实例都是constexpr。是什么技术限制阻止我这样做?”
可能除了标准委员会认为这不是个好主意外,没有其他任何限制。就个人而言,我认为这没有多少用处……我不想限制人们如何使用我的类,只是定义当他们使用它时我的类应该如何行事。当他们使用它时,他们可以将特定实例声明为constexpr(如上所述)。如果我有一些需要使用constexpr实例的代码块,我会使用模板来实现:
template <S s>
function int bar(){return s.xVal;}

int main()
{
   cout << "Hello World" << foo<bar<s>()>( )<< endl; 

   return 0;
}

我认为您最好使用一个constexpr函数,这样它就可以在严格和非严格的方式下都可以使用。

constexpr int bar(S s) { return s.xVal; }

我期望constexpr数据成员将产生一个类,其中所有实例都可以声明为constexpr。也就是说,在编译期间,尝试创建具有非constexpr值的对象将被拒绝。 - KnowItAllWannabe
@KnowItAllWannabe constexpr并不是对使用方式的限制,而是允许在只允许使用constexpr的地方使用它。如果给constexpr函数传递非constexpr参数,则会在运行时而不是编译时运行...这是一件好事,因为我不想为每种情况复制我的代码(就像我以前必须做的那样)。任何可以作为constexpr实例化的类都可以始终具有不是constexpr的实例(我只需要调用new S即可)。这有意义吗? - IdeaHat
对于声明为constexpr的对象的可变数据成员,甚至可以说constexpr意味着在编译时已知值。类型上的constexpr确实是一种限制;人们可以想象constexpr数据成员需要使用常量表达式进行初始化。也许,这种好处太小了,或者constexpr的原始意图被非静态数据成员所违反。 - dyp
@IdeaHat:这是有道理的,我明白,但我认为也有意义希望确保一个类的所有实例在编译期间都有已知的值。 - KnowItAllWannabe
1
@IdeaHat:但现在你在争论非静态constexpr数据成员会是不好的设计,而你提出的答案似乎表明这些数据成员的值在编译期间无法确定。(你的例子证明了它们的值可以被确定,只要它们被初始化为constexpr值。)我的根本兴趣在于知道是否存在某些技术限制,使得非静态constexpr数据成员无法实现。 - KnowItAllWannabe
显示剩余12条评论

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