정적 constexpr char []에 대한 정의되지 않은 참조


186

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
}

1
그냥 직감, 예를 들어 baz가 정 수면 작동합니까? 그런 다음 액세스 할 수 있습니까? 버그 일 수도 있습니다.
FailedDev

1
@Pubby : 질문 : 어떤 번역 단위가 정의됩니까? 답 : 헤더를 포함하는 모든 것. 문제 : 하나의 정의 규칙을 위반합니다. 예외 : 컴파일 타임 상수 적분은 헤더에서 "초기화"될 수 있습니다.
Mooing Duck

int@MooingDuck으로 잘 컴파일 됩니다. 비 멤버로 잘 작동합니다. 그것은 규칙을 위반하지 않습니까?
Pubby

@ Pubby8 : int사기꾼. C ++ 11 (가능한)에 대한 규칙이 변경되지 않는 한 비회원으로서 허용해서는 안됩니다.
Mooing Duck

견해와 공감대를 고려할 때이 질문에는보다 자세한 답변이 필요했습니다. 아래에 추가했습니다.
Shafik Yaghmour

답변:


188

cpp 파일에 추가하십시오 :

constexpr char foo::baz[];

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


70
컴파일러에 이전에 없었던 정보를 제공하지 않는 것 같기 때문에 이상해 보입니다 ...
포도 나무

32
.cpp 파일에 클래스 선언이 있으면 더 이상하게 보입니다! 클래스 선언에서 필드를 초기화하지만 클래스 아래에 constexpr char foo :: baz []를 작성하여 필드 를 " 선언 " 해야합니다 . constexpr을 사용하는 프로그래머는 이상한 팁을 따라 프로그램을 컴파일 할 수 있습니다. 다시 선언하십시오.
Lukasz Czerwinski

5
@LukaszCzerwinski : 찾고있는 단어는 "define"입니다.
Kerrek SB

5
맞습니다. 새로운 정보는 없습니다 :decltype(foo::baz) constexpr foo::baz;
not-a-user

6
foo가 템플릿 화되면 표현이 어떻게 보일까요? 감사.
Hei

80

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 로 인해 변경되었지만 이러한 변경 으로 인해이 사례의 결과는 변경되지 않습니다.


우리 constexpr는 그것과 전혀 관련이 없다는 것이 분명 합니까? ( baz어쨌든 상수 표현입니다)
MM

멤버가 아닌 경우 @MattMcNabb well constexpr 이 필요합니다. integral or enumeration type그렇지 않으면 중요한 것은 상수 표현식이라는 것 입니다.
Shafik Yaghmour

첫 번째 단락에서 "사용 된"은 "홀수 사용 된"으로 표시되어야한다고 생각하지만 C ++에 대해서는 확신이 없습니다.
Egor Pasko

37

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

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

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


4
C ++ 17 정보를 제공합니다. 이 정보를 허용 된 답변에 추가 할 수 있습니다!
SR

5

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

static constexpr char * baz = "quz";

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


9
을 사용 char[]하면 sizeof컴파일 타임에 문자열 길이를 가져올 수 있지만 사용할 char *수 없습니다 (이 경우 포인터 유형의 너비를 반환합니다).
gnzlbg 2016 년

2
ISO C ++ 11을 엄격하게 사용하려는 경우에도 경고가 생성됩니다.
Shital Shah

sizeof문제 를 나타내지 않으며 "헤더 전용"솔루션에 사용할 수있는 내 답변을 참조하십시오.
Josh Greifer

4

정적 멤버의 외부 연결에 대한 내 해결 방법은 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]'

-1

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.