C ++에서 자율적`self` 멤버 유형을 구현할 수 있습니까?


101

C ++ 에는 PHP의 키워드 와 동등한 기능 이 없습니다 .self 둘러싸는 클래스의 유형으로 평가되는 .

클래스별로 위조하는 것은 쉽습니다.

struct Foo
{
   typedef Foo self;
};

그러나 나는 Foo다시 써야 했다. 언젠가는 이것이 잘못되어 조용한 버그를 일으킬 수 있습니다.

decltype이 작업을 "자율적으로"만들기 위해 및 친구들의 조합을 사용할 수 있습니까 ? 나는 이미 다음을 시도 하지만 this그 곳에서는 유효하지 않습니다 :

struct Foo
{
   typedef decltype(*this) self;
};

// main.cpp:3:22: error: invalid use of 'this' at top level
//     typedef decltype(*this) self;

( static동일한 작업을 수행하지만 후기 바인딩을 사용 하는)에 대해서는 걱정하지 않을 것 입니다.


9
this_t아마도 일반 C ++ 명명과 더 잘 맞을 것입니다.
Bartek Banachewicz

3
@BartekBanachewicz : 또는 this_type
PlasmaHH

10
이 제안이었다 여부,하지만 누군가가 제안하는 경우 @Praetorian, 내가 기억할 수 auto()~auto()ctors에 대한 / DTORS. 최소한의 말이 흥미 롭습니다. 그 목적으로 사용된다면 아마도 typedef auto self;.하지만 그것은 나에게 약간 스케치처럼 보입니다.
chris

11
나는이 가능하게하는 구문을 제안 할 예정인지 솔직히, 아마 것이다 decltype(class)아마로, decltype(struct)해당. auto특정 컨텍스트에서 보다 훨씬 명확하며 .NET 기반 언어에 맞는 문제는 없습니다 decltype(auto).
chris

11
오류를 피하고 싶기 때문에, void _check() { static_assert(std::is_same<self&, decltype(*this)>::value, "Correct your self type"); }클래스 템플릿에서 작동하지 않는 것처럼 static_assert로 더미 멤버 함수를 설정할 수 있습니다 ...
milleniumbug

답변:


39

다음은 Foo 유형을 반복하지 않고 수행 할 수있는 방법입니다.

template <typename...Ts>
class Self;

template <typename X, typename...Ts>
class Self<X,Ts...> : public Ts...
{
protected:
    typedef X self;
};

#define WITH_SELF(X) X : public Self<X>
#define WITH_SELF_DERIVED(X,...) X : public Self<X,__VA_ARGS__>

class WITH_SELF(Foo)
{
    void test()
    {
        self foo;
    }
};

에서 파생 하려면 다음과 같은 방식으로 Foo매크로 WITH_SELF_DERIVED를 사용해야합니다 .

class WITH_SELF_DERIVED(Bar,Foo)
{
    /* ... */
};

원하는만큼 많은 기본 클래스를 사용하여 다중 상속을 수행 할 수도 있습니다 (가변 템플릿 및 가변 매크로 덕분에).

class WITH_SELF(Foo2)
{
    /* ... */
};

class WITH_SELF_DERIVED(Bar2,Foo,Foo2)
{
    /* ... */
};

gcc 4.8 및 clang 3.4에서 작동하는지 확인했습니다.


18
대답은 "아니요,하지만 Ralph는 할 수 있습니다!"라고 생각합니다. ;)
궤도의 Lightness Races

3
이것이 단순히 typedef를 넣는 것보다 어떻게 더 우월합니까? 그리고 신, 왜 typedef가 필요합니까? 왜?
Miles Rout

7
@MilesRout 이것은 대답이 아니라 질문에 대한 질문입니다. 소프트웨어 개발 (특히 유지 관리)의 많은 경우 코드의 중복을 피하는 것이 도움이되므로 한 곳에서 무언가를 변경할 때 다른 곳에서 코드를 변경할 필요가 없습니다. 그것이 autodecltype또는이 경우의 요점 입니다 self.
Ralph Tandetzky

1
template<typename T>class Self{protected: typedef T self;}; class WITH_SELF(Foo) : public Bar, private Baz {};더 간단하고 상속에 대한 더 정확한 제어를 허용 할 것입니다.
Aconcagua

@mmmmmmmm, "자신을 반복하지 마십시오"원칙을 깊이 이해하지 못했다면 아직 충분히 / 진지하게 코딩하지 않았을 가능성이 있습니다. 이 "혼잡함"(실제로는 멀리 떨어져 있음)은 비 우아한 언어 기능 (또는 잘못된 기능, 심지어 엄격한 조치에 의한 결함)에 대해 이야기하는 맥락에서 상당히 우아한 수정입니다.
Sz.

38

가능한 해결 방법 (유형을 한 번만 작성해야하므로) :

template<typename T>
struct Self
{
protected:
    typedef T self;
};

struct Foo : public Self<Foo>
{
    void test()
    {
        self obj;
    }
};

더 안전한 버전을 위해 T실제로 다음에서 파생 된다는 것을 확인할 수 있습니다 Self<T>.

Self()
{
    static_assert(std::is_base_of<Self<T>, T>::value, "Wrong type passed to Self");
}

• 그래도주의 static_assert멤버 함수 내부 아마 유형에 전달로 확인하는 유일한 방법입니다 std::is_base_of완료해야합니다.


4
typenametypedef에는 필요 없습니다 . 그리고 이것은 중복의 수를 줄이지 않기 때문에 실행 가능한 대안이라고 생각하지 않습니다.
Konrad Rudolph

Foo이름 을 반복하는 것과 똑같은 문제가 있습니다.
Bartek Banachewicz

6
그것은 이다 반복은 서로 매우 가까이 있기 때문에,하지만, 변두리에 더 원래의 방식보다. 질문에 대한 해결책은 아니지만 최상의 해결 방법을 시도하려면 +1하십시오.
궤도

4
나는 그 솔루션을 몇 번 사용했고 한 가지 나쁜 점이 있습니다. 나중에에서 파생 될 때 다음 Foo중 하나를 수행해야합니다. (1) T를 리프 하위 항목으로 위쪽으로 전파하거나 (2) SelfT에서 여러 번 상속해야합니다. , 또는 (3) 모든 아이들이 Base .. 사용 가능하지만 예쁘지 않다는 것을 받아들이십시오.
quetzalcoatl 2014 년

@quetzalcoatl : self대신 복제를 시도하고 있기 때문에 static문제가되지 않습니다.
궤도에서 가벼운 경주

33

정규 클래스 선언 대신 매크로를 사용할 수 있습니다.

#define CLASS_WITH_SELF(X) class X { typedef X self;

그리고 다음과 같이 사용하십시오.

CLASS_WITH_SELF(Foo) 
};

#define END_CLASS }; 가독성에 도움이 될 것입니다.


@Paranaix를 가져 와서 Self사용할 수도 있습니다 (정말 hackish가 시작됩니다)

#define WITH_SELF(X) X : public Self<X>

class WITH_SELF(Foo) {
};

18
EWWWW END_CLASS. 그것은 완전히 불필요합니다.
Puppy

31
@DeadMG 어떤 사람들은 더 일관성을 좋아할 것이라고 생각합니다. 결국, 첫 번째 매크로 사용은 끝나지 않습니다 {(가) 때문에, }텍스트 편집기는 아마 싫어하는 것 "교수형", 너무.
Bartek Banachewicz

6
좋은 생각이지만 매크로에 근본적으로 반대하지는 않지만 C ++ 범위 지정을 모방 한 경우에만 사용을 받아 들일 CLASS_WITH_SELF(foo) { … };것입니다.
Konrad Rudolph

2
@KonradRudolph 저도 그렇게하는 방법을 추가했습니다. 아니 난 그냥 완전성을 위해, 그것을 좋아하는
Bartek Banachewicz

1
하지만 이러한 접근 방식에는 몇 가지 문제가 있습니다. 첫 번째는 (다른 매크로 매개 변수를 사용하지 않는 한) 클래스를 쉽게 상속하도록 허용하지 않으며, 두 번째는 상속 하는 모든 문제 를 Self가지고 있습니다.
Bartek Banachewicz

31

긍정적 인 증거는 없지만 불가능하다고 생각 합니다. 다음은 실패합니다 – 당신의 시도와 같은 이유로 – 그리고 저는 그것이 우리가 얻을 수있는 가장 먼 것이라고 생각합니다.

struct Foo {
    auto self_() -> decltype(*this) { return *this; }

    using self = decltype(self_());
};

본질적으로 이것이 보여주는 것은 typedef를 선언하려는 범위가 단순히에 대한 액세스 권한 이 없고 (직접적이든 간접적이든) this클래스의 유형이나 이름을 얻는 다른 (컴파일러 독립적) 방법이 없다는 것입니다.


4
이것이 C ++ 1y의 반환 유형 추론으로 가능할까요?
dyp

4
@dyp 아무것도 변경하지 않는 내 대답의 목적을 위해. 여기서 오류는 후행 반환 유형이 아니라 호출에 있습니다.
Konrad Rudolph

1
@quetzalcoatl :의 내부 decltype는 평가되지 않은 컨텍스트이므로 멤버 함수를 호출하는 것은 문제가 아닙니다 (시도되지 않음)
Lightness Races in Orbit

1
@TomKnapen clang으로 시도하면 실패합니다. 내가 아는 한 GCC에서 수락했다는 사실은 버그입니다.

4
FWIW 는 비 정적 데이터 멤버 인 struct S { int i; typedef decltype(i) Int; };경우에도 작동합니다 i. decltype단순한 이름이 표현식으로 평가되지 않는 특별한 예외가 있기 때문에 작동 합니다. 그러나 나는 질문에 답하는 방식으로이 가능성을 사용하는 방법을 생각할 수 없다.

21

GCC와 clang 모두에서 작동 하는 것은 함수 typedef의 후행 반환 유형에서 this사용하여 참조하는 typedef를 만드는 것 this입니다. 이것은 정적 멤버 함수의 선언이 아니기 때문에의 사용 this이 허용됩니다. 그런 다음 해당 typedef를 사용하여 self.

#define DEFINE_SELF() \
    typedef auto _self_fn() -> decltype(*this); \
    using self = decltype(((_self_fn*)0)())

struct Foo {
    DEFINE_SELF();
};

struct Bar {
    DEFINE_SELF();
};

불행히도 표준을 엄격하게 읽으면 이것이 유효하지 않다고 말합니다. clang이하는 일은 this정적 멤버 함수의 정의에 사용되지 않는 것을 확인하는 것 입니다. 그리고 여기에서는 실제로 그렇지 않습니다. GCC는 this함수의 종류에 관계없이 후행 반환형으로 사용 되어도 상관 없으며, static멤버 함수 에서도 가능합니다 . 그러나 표준이 실제로 요구하는 것은this 것은 비 정적 멤버 함수 (또는 비 정적 데이터 멤버 이니셜 라이저)의 정의 외부에서 사용되지 않는다는 것입니다. 인텔은이를 제대로 받아들이고이를 거부합니다.

을 고려하면:

  • this 비 정적 데이터 멤버 이니셜 라이저 및 비 정적 멤버 함수 ([expr.prim.general] p5)에서만 허용됩니다.
  • 비 정적 데이터 멤버는 이니셜 라이저 ([dcl.spec.auto] p5)에서 유형을 추론 할 수 없습니다.
  • 비 정적 멤버 함수는 함수 호출 컨텍스트에서 정규화되지 않은 이름으로 만 참조 될 수 있습니다 ([expr.ref] p4).
  • 비 정적 멤버 함수는 평가되지 않은 컨텍스트에서도 정규화되지 않은 이름으로 만 호출 될 this수 있습니다 ([over.call.func] p3),
  • 정규화 된 이름 또는 멤버 액세스로 비 정적 멤버 함수에 대한 참조에는 정의중인 형식에 대한 참조가 필요합니다.

self어떤 식 으로든 어딘가에 형식 이름을 포함하지 않고는 구현할 방법이 전혀 없다고 결론적으로 말할 수 있다고 생각 합니다.

편집 : 이전 추론에 결함이 있습니다. "비 정적 멤버 함수는 평가되지 않은 컨텍스트에서도 사용할 수있는 경우 ([over.call.func] p3) 정규화되지 않은 이름으로 만 호출 할 수 있습니다."가 잘못되었습니다. 그것은 무엇 실제로 말한다 것은

키워드 this(9.3.2)가 범위 내에 있고 class T또는 파생 클래스 T를 참조하는 경우 암시 된 개체 인수는 (*this)입니다. 키워드 this가 범위에 없거나 다른 클래스를 참조하는 경우 인위적인 유형의 개체 T가 암시 된 개체 인수가됩니다. 인수 목록이 인위적인 객체에 의해 증가되고 오버로드 해결이의 비 정적 멤버 함수 중 하나를 선택 T하면 호출 형식이 잘못됩니다.

정적 멤버 함수 내부에는 this나타나지 않을 수 있지만 여전히 존재합니다.

그러나 주석에 따라 정적 멤버 함수 내에서 f()to 의 변환 (*this).f()이 수행되지 않고 수행되지 않으면 [expr.call] p1이 위반됩니다.

[...] 멤버 함수 호출의 경우 접미사 식은 암시 적 (9.3.1, 9.4) 또는 명시 적 클래스 멤버 액세스 (5.2.5) 여야하며 [...]

회원 액세스 권한이 없기 때문입니다. 그래서 그것조차 작동하지 않을 것입니다.


나는 [class.mfct.non 정적] / 3가 말한다 생각 _self_fn_1()으로 "변화"입니다 (*this)._self_fn_1(). 그것이 불법인지 확실하지 않습니다.
dyp

@dyp "사용할 수 X있는 상황에서 클래스의 멤버 this로 사용된다"고해서 변환이 수행되지 않는다고 생각합니다.

1
그러나 이것은 암시 적이거나 명시적인 클래스 멤버 액세스가 아닙니다 ..? [expr.call] / 1 "멤버 함수 호출의 경우 접미사 표현식은 암시 적 또는 명시 적 클래스 멤버 액세스 [...]"
dyp

(내 말은, 당신이 가지고있을 때 무슨 일이 일어나는가 auto _self_fn_1() -> decltype(*this); auto _self_fn_1() const -> decltype(*this);?)
dyp

@dyp [expr.call] / 1은 좋은 점입니다. 자세히 살펴 보겠습니다. 정보 const과부하, 비록 : 그 문제가되지 않습니다. 5.1p3 특히 정적 멤버 함수에 적용 변형 및 유형 말한다 된 this없다 Foo*/ Bar*(않고 const전혀 없기 때문에,) const의 선언 _self_fn_2.

17
#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }
#define SELF(T) typedef T self; SELF_CHECK(T)

struct Foo {
  SELF(Foo); // works, self is defined as `Foo`
};
struct Bar {
  SELF(Foo); // fails
};

이것은 self_check호출되지 않은 템플릿 유형에서 작동하지 static_assert않으므로 평가되지 않습니다.

templates에서도 작동하도록 몇 가지 해킹을 할 수 있지만 실행 시간이 약간 소요됩니다.

#define TESTER_HELPER_TYPE \
template<typename T, std::size_t line> \
struct line_tester_t { \
  line_tester_t() { \
    static_assert( std::is_same< decltype(T::line_tester), line_tester_t<T,line> >::value, "test failed" ); \
    static_assert( std::is_same< decltype(&T::static_test_zzz), T*(*)() >::value, "test 2 failed" ); \
  } \
}

#define SELF_CHECK( SELF ) void self_check() { static_assert( std::is_same< typename std::decay<decltype(*this)>::type, SELF >::value, "self wrong type" ); }

#define SELF(T) typedef T self; SELF_CHECK(T); static T* static_test_zzz() { return nullptr; }; TESTER_HELPER_TYPE; line_tester_t<T,__LINE__> line_tester

struct크기가 1 바이트는 클래스에 생성됩니다. 유형이 인스턴스화되면 self테스트 대상입니다.


그것도 나쁘지 않습니다!
궤도의 가벼운 경주

@LightnessRacesinOrbit는 이제 template클래스 지원 옵션이 있습니다.
Yakk-Adam Nevraumont 2014 년

나는 어제 퇴근 할 때 이것에 대해 정확히 생각하고 있었다. 당신은 나를 이겼습니다 :). 링크 문제를 피하기 위해 self_check ()를 인라인으로 선언하는 것이 좋습니다 (여러 개체 파일에서 발견되는 동일한 기호 Foo :: self_check ()).
돼지

1
@theswine : 9.3 / 2는 C ++ 표준에있는 단락의 색인 으로, 클래스 정의 본문에 정의 된 클래스 멤버 함수 가 이미 암시 적으로 inline. 즉 inline, 전혀 쓸 필요가 없습니다 . 따라서 inline전체 경력에 대한 모든 클래스 멤버 함수 정의 앞에 글을 쓰고 있다면 지금 중단 할 수 있습니다.;)
Lightness Races in Orbit

2
@LightnessRacesinOrbit 아, 실제로 나는 그랬다. 감사합니다. 그러면 나중에 타이핑을 할 필요가 없습니다. :). 나는 C ++에 대해 얼마나 잘 모르는지 항상 놀랍습니다.
돼지

11

나는 또한 불가능하다고 생각합니다. 여기에 또 다른 실패했지만 IMHO 흥미로운 시도가 있습니다 this.

template<typename T>
struct class_t;

template<typename T, typename R>
struct class_t< R (T::*)() > { using type = T; };

struct Foo
{
   void self_f(); using self = typename class_t<decltype(&self_f)>::type;
};

#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo >::value, "" );
}

C ++ self_f에서 주소를 가져오고 싶을 때 클래스 로 자격을 부여해야하기 때문에 실패합니다.


int T::*멤버 변수에 대한 일반 포인터 에서도 동일한 문제가 발생 합니다. 그리고 int self_var; typedef decltype(&self_var) self_ptr작동하지도 않습니다 int*.
MSalters

9

나는 최근에 그것이 brace-or-equal-initializer*this 에서 허용 된다는 것을 발견했습니다 . (§ 5.1.1에 기술 으로부터 n3337 작업 초안 ) :

3 [..] 다른 컨텍스트의 객체 표현식과 달리 *this, 멤버 함수 본문 외부에서 클래스 멤버 액세스 (5.2.5)를 위해 완전한 유형일 필요는 없습니다. [..]

4 그렇지 않고 멤버 선언 자가 클래스 X의 비 정적 데이터 멤버 (9.2)를 선언하는 경우 표현식 this은 선택적 brace-or-equal-initializer 내에서 "X에 대한 포인터"유형의 prvalue입니다 . member-declarator의 다른 곳에 표시되지 않습니다 .

5 this다른 문맥에서는 표현을 사용 해서는 안됩니다. [ 예 :

class Outer {
    int a[sizeof(*this)];               // error: not inside a member function
    unsigned int sz = sizeof(*this);    // OK: in brace-or-equal-initializer

    void f() {
        int b[sizeof(*this)];           // OK
        struct Inner {
            int c[sizeof(*this)];       // error: not inside a member function of Inner
        };
    }
};

최종 예 ]

이를 염두에두고 다음 코드 :

struct Foo
{
    Foo* test = this;
    using self = decltype(test);

    static void smf()
    {
        self foo;
    }
};

#include <iostream>
#include <type_traits>

int main()
{
    static_assert( std::is_same< Foo::self, Foo* >::value, "" );
}

통과 다니엘 프레이의 static_assert .

Live example


test하지만 성가신 쓸모없는 변수 가 있습니다
MM

@Matt True,하지만 여전히 흥미 롭습니다.

1
이것은 없이도 효과가 있었을 것입니다 = this. 그리고 왜 그냥using self = Foo*;
user362515

1
우리는 test유형, 음, Foo *!
Paul Sanders

4

유형이 포함되어있는 클래스의 멤버 유형으로 필요하지 않는 한 당신의 사용을 대체 할 수있는 self과를 decltype(*this). 코드의 여러 위치에서 사용하는 경우 SELF다음과 같이 매크로 를 정의 할 수 있습니다 .

#define SELF decltype(*this)

2
그리고 당신은, 또는 중첩 된 클래스에 클래스의 외부에서 사용할 수 없습니다
Drax

1
@Drax : 수업 밖에서는 사용할 수 없습니다.
Ben Voigt 2014 년

@BenVoigt 그러나 가장 흥미로운 사용 사례 인 IMO 인 중첩 된 클래스에서 사용할 수 있습니다.
Drax

1
나는 그렇게 생각하지 않는다. 안 self외부 클래스를 직접 둘러싸는 클래스를 참조하지? 그러나 나는 PHP를 잘 모른다.
Ben Voigt 2014 년

1
@LightnessRacesinOrbit : 코드와 오류가 "PHP에 중첩 된 유형이 없습니다"라고 말하는 것 같습니까?
Ben Voigt 2014 년

1

내 버전을 제공하십시오. 가장 좋은 점은 사용이 네이티브 클래스와 동일하다는 것입니다. 그러나 템플릿 클래스에서는 작동하지 않습니다.

template<class T> class Self;

#define CLASS(Name) \
class Name##_; \
typedef Self<Name##_> Name; \
template<> class Self<Name##_>

CLASS(A)
{
    int i;
    Self* clone() const { return new Self(*this); }
};

CLASS(B) : public A
{
    float f;
    Self* clone() const { return new Self(*this); }
};

1

hvd의 답변을 바탕으로 누락 된 유일한 것은 참조를 제거하는 것이므로 std :: is_same 검사가 실패하는 이유를 발견했습니다 (b / c 결과 유형은 실제로 유형에 대한 참조입니다). 이제이 매개 변수없는 매크로가 모든 작업을 수행 할 수 있습니다. 아래의 작업 예 (GCC 8.1.1 사용).

#define DEFINE_SELF \
    typedef auto _self_fn() -> std::remove_reference<decltype(*this)>::type; \
    using self = decltype(((_self_fn*)0)())

class A {
    public:
    DEFINE_SELF;
};

int main()
{
    if (std::is_same_v<A::self, A>)
        std::cout << "is A";
}

GCC 이외의 다른 컴파일러에서는 컴파일되지 않습니다.
zedu

0

나는 "스스로해야한다"는 명백한 해결책을 반복 할 것이다. 이것은 간단한 클래스와 클래스 템플릿 모두에서 작동하는 간결한 C ++ 11 버전의 코드입니다.

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<decltype(*((TySelf*)(0))), \
            decltype(*this)>::value, "TySelf is not what it should be"); \
    } \
    enum { static_self_check_token = __LINE__ }; \
    static_assert(int(static_self_check_token) == \
        int(TySelf::static_self_check_token), \
        "TySelf is not what it should be")

ideone 에서 실제 동작을 볼 수 있습니다 . 이 결과로 이어지는 기원은 다음과 같습니다.

#define DECLARE_SELF(Type) typedef Type _TySelf; /**< @brief type of this class */

struct XYZ {
    DECLARE_SELF(XYZ)
};

이것은 코드를 다른 클래스에 복사하여 붙여넣고 XYZ를 변경하는 것을 잊어 버리는 명백한 문제가 있습니다.

struct ABC {
    DECLARE_SELF(XYZ) // !!
};

내 첫 번째 접근 방식은 그다지 독창적이지 않았습니다. 다음과 같은 함수를 만들었습니다.

/**
 *  @brief namespace for checking the _TySelf type consistency
 */
namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *
 *  @tparam _TySelf is reported self type
 *  @tparam _TyDecltypeThis is type of <tt>*this</tt>
 */
template <class _TySelf, class _TyDecltypeThis>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 *  @tparam _TySelf is reported self type (same as type of <tt>*this</tt>)
 */
template <class _TySelf>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TySelf, _TySelf> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

/**
 *  @brief helper function for self-check, this is used to derive type of this
 *      in absence of <tt>decltype()</tt> in older versions of C++
 *
 *  @tparam _TyA is reported self type
 *  @tparam _TyB is type of <tt>*this</tt>
 */
template <class _TyA, class _TyB>
inline void __self_check_helper(_TyB *UNUSED(p_this))
{
    typedef CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<_TyA, _TyB>)> _TyAssert;
    // make sure that the type reported as self and type of *this is the same
}

/**
 *  @def __SELF_CHECK
 *  @brief declares the body of __self_check() function
 */
#define __SELF_CHECK \
    /** checks the consistency of _TySelf type (calling it has no effect) */ \
    inline void __self_check() \
    { \
        __self::__self_check_helper<_TySelf>(this); \
    }

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK

} // ~self

다소 길지만 여기서는 참아주세요. 이 함수는 .NET Framework 형식을 추론하는 데 사용 decltype되므로 C ++ 03 __self_check_helper에서 this. 또한는 static_assert없지만 sizeof()대신 트릭이 사용됩니다. C ++ 0x의 경우 훨씬 더 짧게 만들 수 있습니다. 이제 이것은 템플릿에서 작동하지 않습니다. 또한, 매크로 현학적으로 컴파일 할 경우, 그것은 여분의 불필요한 세미콜론에 대해 불평한다 (또는 홀수 찾고 매크로의 본문에 세미콜론으로 끝나지 않는 남아있을 것입니다, 마지막에 세미콜론을 기대하지 않고있는 사소한 문제가 XYZABC).

Type전달 된 에 대해 확인 하는 것은 DECLARE_SELF옵션이 아닙니다 . 이는 오류 XYZ가 있는 클래스를 확인 하지 않고 ABC( 괜찮음) 클래스 만 확인하기 때문 입니다. 그리고 그것은 나를 때렸다. 템플릿과 함께 작동하는 추가 스토리지 제로 비용 솔루션 :

namespace __self {

/**
 *  @brief compile-time assertion (_TySelf must be declared the same as the type of class)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<true> {};

/**
 *  @def DECLARE_SELF
 *  @brief declares _TySelf type and adds code to make sure that it is indeed a correct one
 *  @param[in] Type is type of the enclosing class
 */
#define DECLARE_SELF(Type) \
    typedef Type _TySelf; /**< @brief type of this class */ \
    __SELF_CHECK \
    enum { __static_self_check_token = __LINE__ }; \
    typedef __self::CStaticAssert<sizeof(CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<int(__static_self_check_token) == int(_TySelf::__static_self_check_token)>)> __static_self_check

} // ~__self 

이것은 단순히 고유 한 열거 형 값 (또는 한 줄에 모든 코드를 작성하지 않는 경우 적어도 고유 한 경우)에 대한 정적 주장을 만들고, 유형 비교 속임수를 사용하지 않으며 템플릿에서도 정적 주장으로 작동합니다. . 그리고 보너스로-이제 마지막 세미콜론이 필요합니다 :).

좋은 영감을 주신 Yakk에게 감사드립니다. 나는 그의 대답을 먼저 보지 않고 이것을 쓰지 않을 것입니다.

VS 2008 및 g ++ 4.6.3으로 테스트되었습니다. 실제로 XYZABC예제를 사용하면 다음 과 같이 불평합니다.

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp:91:5: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â
self.cpp:91:5: error: template argument 1 is invalid
self.cpp: In function âvoid __self::__self_check_helper(_TyB*) [with _TyA = XYZ, _TyB = ABC]â:
self.cpp:91:5:   instantiated from here
self.cpp:58:87: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<XYZ, ABC

이제 ABC를 템플릿으로 만들면 :

template <class X>
struct ABC {
    DECLARE_SELF(XYZ); // line 92
};

int main(int argc, char **argv)
{
    ABC<int> abc;
    return 0;
}

우리는 얻을 것이다:

ipolok@ivs:~$ g++ self.cpp -c -o self.o
self.cpp: In instantiation of âABC<int>â:
self.cpp:97:18:   instantiated from here
self.cpp:92:9: error: invalid application of âsizeofâ to incomplete type â__self::CSELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE2<false>â

함수 검사가 (예상대로) 컴파일되지 않았기 때문에 행 번호 검사 만 트리거되었습니다.

C ++ 0x (그리고 악한 밑줄없이)를 사용하려면 다음이 필요합니다.

namespace self_util {

/**
 *  @brief compile-time assertion (tokens in class and TySelf must match)
 *  @tparam b_check is the asserted value
 */
template <bool b_check>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE;

/**
 *  @brief compile-time assertion (specialization for assertion passing)
 */
template <>
class SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<true> {};

/**
 *  @brief static assertion helper type
 *  @tparam n_size is size of object being used as assertion message
 *      (if it's a incomplete type, compiler will display object name in error output)
 */
template <const size_t n_size>
class CStaticAssert {};

#define SELF_CHECK \
    /** checks the consistency of TySelf type (calling it has no effect) */ \
    void self_check() \
    { \
        static_assert(std::is_same<TySelf, decltype(*this)>::value, "TySelf is not what it should be"); \
    }

#define DECLARE_SELF(Type) \
    typedef Type TySelf; /**< @brief type of this class */ \
    SELF_CHECK \
    enum { static_self_check_token = __LINE__ }; \
    typedef self_util::CStaticAssert<sizeof(SELF_TYPE_MUST_BE_THE_SAME_AS_CLASS_TYPE<int(static_self_check_token) == int(TySelf::static_self_check_token)>)> static_self_check

} // ~self_util

CStaticAssert 비트는 템플릿 본문에서 typedef-ed 된 유형을 생성하므로 유감스럽게도 여전히 필요하다고 생각합니다 (으로 동일하게 할 수 없다고 가정합니다 static_assert). 이 접근 방식의 장점은 여전히 ​​비용이 전혀 들지 않는다는 것입니다.


본질적으로 static_assert여기서 다시 구현하고 있지 않습니까? 또한 잘못된 (예약 된) 식별자를 사용하고 있기 때문에 전체 코드가 유효하지 않습니다.
Konrad Rudolph

@KonradRudolph 네, 사실입니다. 당시에는 C ++ 0x가 없으므로 완전한 답변을 제공하기 위해 static_assert를 다시 구현했습니다. 나는 대답에서 그것을 말한다. 유효하지 않습니까? 방법을 지적 할 수 있습니까? 잘 컴파일되어 지금 사용하고 있습니다.
돼지

1
C ++는 컴파일러에 대해 전역 범위에서 두 개의 선행 밑줄뿐만 아니라 선행 밑줄 다음에 대문자가 오는 모든 항목을 예약하므로 식별자가 유효하지 않습니다. 사용자 코드는이를 사용해서는 안되지만 모든 컴파일러가이를 오류로 표시하지는 않습니다.
Konrad Rudolph

@KonradRudolph 나는 그것을 몰랐다. 나는 그것을 사용하는 많은 코드를 가지고 있으며 Linux / Mac / Windows에서 문제가 없었습니다. 하지만 알아두면 좋은 것 같아요.
돼지는

0

이 엉뚱한 템플릿에 대해 모두 알지 못합니다. 매우 간단한 것은 어떻습니까?

#define DECLARE_TYPEOF_THIS typedef CLASSNAME typeof_this
#define ANNOTATED_CLASSNAME(DUMMY) CLASSNAME

#define CLASSNAME X
class ANNOTATED_CLASSNAME (X)
{
public:
    DECLARE_TYPEOF_THIS;
    CLASSNAME () { moi = this; }
    ~CLASSNAME () { }
    typeof_this *moi;
    // ...
};    
#undef CLASSNAME

#define CLASSNAME Y
class ANNOTATED_CLASSNAME (Y)
{
    // ...
};
#undef CLASSNAME

몇 가지 매크로를 참을 수 없다면 일이 끝났습니다. CLASSNAME생성자 (물론 소멸자)를 선언 하는 데 사용할 수도 있습니다 .

라이브 데모 .


1
그것은 클래스 / 후 사용해야합니다 수있는 방법에 대한 매우 뚜렷한 효과가
궤도의 밝기 경주

@LightnessRacesinOrbit 어떻게 그렇게? 나는 그것을 보지 않는다. 나는 반성하여 원래 게시물의 마지막 문장을 제거했습니다. 내가 원래 가지고 있던 것이 당신이 이것을 생각하게 만들었을 것입니다.
Paul Sanders
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.