Подтвердить что ты не робот

Почему не могут нестатические члены данных быть constexpr?

Это действительный код:

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 };
};

Но здесь я бы очень хотел объявить xVal и yVal constexpr - вот так:

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. Но почему?

4b9b3361

Ответ 1

Подумайте, что означает constexpr. Это означает, что я могу разрешить это значение во время компиляции.

Таким образом, переменная-член класса не может сама быть constexpr... экземпляр, к которому принадлежит xVal, не существует до момента создания экземпляра! Вещь, которой принадлежит xVal, может быть constexp, и это сделает xVal a constexpr, но xVal никогда не может быть constexpr самостоятельно.

Это не означает, что эти значения не могут быть const-выражением... на самом деле, экземпляр constexpr класса может использовать переменные как выражения const:

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 может быть в совершенно разных единицах компиляции, поэтому факт, что S должен быть constexpr, может не быть известен до времени ссылки, особенно если забыта реализация A. Такие неоднозначные случаи трудно отлаживать и их трудно реализовать. Хуже всего то, что интерфейс для S может быть полностью открыт в общей библиотеке, интерфейсе COM, ect... Это может полностью изменить все инфраструктуры для общей библиотеки, и это, вероятно, будет неприемлемым.

Еще одна причина заключается в том, насколько заразителен. Если какой-либо из членов класса был 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. yVal по сути становится constexpr, потому что xVal есть. У вас нет технической причины компилятора, вы не можете этого сделать (я не думаю), но он не очень похож на С++.

"ОК, но я REEAAALLLY хочу сделать все экземпляры класса 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; }