카테고리 보관물: C++

C++

정적 constexpr char []에 대한 정의되지 않은 참조 만들면 컴파일됩니다. 무슨 일이야? // .hpp struct foo

static const char수업에 배열 을 갖고 싶습니다 . GCC는 불만을 제기하고 사용해야한다고 말 constexpr했지만 이제는 정의되지 않은 참조라고 알려줍니다. 배열을 비 멤버로 만들면 컴파일됩니다. 무슨 일이야?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}



답변

cpp 파일에 추가하십시오 :

constexpr char foo::baz[];

이유 : 정적 멤버 의 정의 와 선언 을 제공해야합니다 . 선언과 이니셜 라이저는 클래스 정의 안에 들어가지만 멤버 정의는 분리되어야합니다.


답변

C ++ 17은 인라인 변수를 소개합니다

C ++ 17 constexpr static은 odr-used 인 경우 out-of-line 정의가 필요한 멤버 변수에 대해이 문제를 해결합니다 . C ++ 17 이전의 세부 사항은이 답변의 후반부를 참조하십시오.

제안 P0386 인라인 변수inline지정자 를 변수 에 적용하는 기능을 소개합니다 . 특히이 경우 정적 멤버 변수를 constexpr의미 inline합니다. 제안은 다음과 같이 말합니다.

인라인 지정자는 변수뿐만 아니라 함수에도 적용 할 수 있습니다. 인라인으로 선언 된 변수는 인라인으로 선언 된 함수와 동일한 의미를 갖습니다. 여러 번역 단위로 동일하게 정의 될 수 있으며, 변수가 사용되는 모든 변환 단위에서 정의되어야하며 프로그램의 동작은 마치 정확히 하나의 변수가 있습니다.

수정 된 [basic.def] p2 :

선언은
…이 아닌 한 정의입니다 .

  • 클래스 정의 외부에서 정적 데이터 멤버를 선언하고 변수는 constexpr 지정자를 사용하여 클래스 내에 정의되었습니다 (이 사용법은 더 이상 사용되지 않습니다. [depr.static_constexpr] 참조).

[depr.static_constexpr]을 추가하십시오 :

이전 C ++ 국제 표준과의 호환성을 위해 constexpr 정적 데이터 멤버는 이니셜 라이저없이 클래스 외부에서 중복 적으로 다시 선언 될 수 있습니다. 이 사용법은 더 이상 사용되지 않습니다. [ 예:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 — 최종 예]


C ++ 14 및 이전

C ++ 03에서는 const 정수 또는 const 열거 형에 클래스 내 이니셜 라이저 만 제공 할 수있었습니다.이를 사용하는 C ++ 11에서는 리터럴 형식constexpr 으로 확장되었습니다 .

C ++ 11에서 우리는 정적에 대한 네임 스페이스 범위 정의를 제공 할 필요가 없습니다 constexpr, 그렇지 않은 경우는 회원 ODR 사용되는 , 우리는 초안 C ++ 11 표준 섹션이를 볼 수 있습니다 9.4.2 [class.static.data] 말한다 ( 앞으로 강조하는 광산 ) :

[…] 리터럴 타입의 정적 데이터 멤버는 constexpr 지정자를 사용하여 클래스 정의에서 선언 할 수 있습니다. 그렇다면 선언은 할당 식인 모든 이니셜 라이저 절이 상수 표현식 인 중괄호 또는 이니셜 이니셜 라이저를 지정해야합니다. [참고 :이 두 경우 모두 멤버가 상수 식으로 나타날 수 있습니다. —end note]
멤버는 프로그램에서 odr-used (3.2)사용하는 경우 네임 스페이스 범위에서 계속 정의되고 네임 스페이스 범위 정의에는 이니셜 라이저가 포함되지 않아야합니다.

그래서 질문은 여기에서 baz odr-used 됩니다.

std::string str(baz); 

대답은 yes 이므로 네임 스페이스 범위 정의도 필요합니다.

그렇다면 변수가 odr-used 인지 어떻게 알 수 있습니까? 3.2 [basic.def.odr] 섹션의 원래 C ++ 11 문구 는 다음과 같습니다.

표현식은 평가되지 않은 피연산자 (Clause 5) 또는 하위 표현식이 아닌 한 잠재적으로 평가됩니다. 상수 식 (5.19) 에 나타나고 lvalue-to-rvalue 변환 (4.1)에
해당 하는 조건충족 되지 않는 개체가 아닌 경우 이름이 잠재적으로 평가되는 식으로 나타나는 변수 는 odr-used 입니다.

따라서 상수 표현식baz 이 생성 되지만 lvalue-to-rvalue 변환은 배열 로 인해 적용 할 수 없으므로 즉시 적용되지 않습니다 . 이것은 [conv.lval] 섹션에서 다루고 있습니다 :baz4.1

비 기능, 비 배열 타입 T 의 glvalue (3.10) 는 prvalue로 변환 될 수 있습니다. […]

무엇이에 적용되는 배열에 대한 포인터 변환 .

[basic.def.odr] 의이 문구는 일부 사례가이 문구로 다루지 않았기 때문에 결함 보고서 712 로 인해 변경되었지만 이러한 변경 으로 인해이 사례의 결과는 변경되지 않습니다.


답변

C ++ 11에서는 다른 모든 종류의 constexpr 전역 변수와 달리 정적 constexpr 멤버 변수는 외부 연결이 있으므로 명시 적으로 정의해야합니다. 다른 사람들이 설명했듯이 이것은 C ++ 11의 결함입니다.

또한 실제로는 모든 용도에 인라인이 될 수 있기 때문에 최적화로 컴파일 할 때 정의없이 정적 constexpr 멤버 변수를 사용하여 실제로 벗어날 수 있다는 점도 주목할 가치가 있지만 최적화하지 않고 컴파일하면 종종 프로그램이 연결되지 않습니다. 이것은 매우 일반적인 숨겨진 함정입니다-프로그램은 최적화로 잘 컴파일되지만 최적화를 끄면 (아마도 디버깅을 위해) 링크되지 않습니다.

그러나 희소식-이 결함은 C ++ 17에서 수정되었습니다! C ++ 17에서는 정적 constexpr 멤버 변수 가 암시 적으로 인라인 입니다. 변수인라인을 적용하는 것은 C ++ 17의 새로운 개념이지만 사실상 어디에서나 명확한 정의가 필요하지 않음을 의미합니다.


답변

변화 BE 더 우아한 해결책이 아니다 char[]으로는 :

static constexpr char * baz = "quz";

이런 식으로 코드 한 줄에 정의 / 선언 / 초기화기를 사용할 수 있습니다.


답변

정적 멤버의 외부 연결에 대한 내 해결 방법은 constexpr참조 멤버 게터 를 사용 하는 것입니다 (@dedzlme의 답변에 대한 주석으로 @gnzlbg 문제가 발생하지 않음).
이 관용구는 내 프로젝트에 여러 개의 .cpp 파일이있는 것을 싫어하고 #includes와 main()함수 로 구성된 숫자를 하나로 제한하려고하기 때문에 나에게 중요 합니다.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'


답변

내 환경에서 gcc vesion은 5.4.0입니다. “-O2″를 추가하면이 컴파일 오류를 해결할 수 있습니다. gcc가 최적화를 요청할 때이 경우를 처리 할 수있는 것 같습니다.


답변