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 이것은 대답이 아니라 질문에 대한 질문입니다. 소프트웨어 개발 (특히 유지 관리)의 많은 경우 코드의 중복을 피하는 것이 도움이되므로 한 곳에서 무언가를 변경할 때 다른 곳에서 코드를 변경할 필요가 없습니다. 그것이 auto및 decltype또는이 경우의 요점 입니다 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의 경우 훨씬 더 짧게 만들 수 있습니다. 이제 이것은 템플릿에서 작동하지 않습니다. 또한, 매크로 현학적으로 컴파일 할 경우, 그것은 여분의 불필요한 세미콜론에 대해 불평한다 (또는 홀수 찾고 매크로의 본문에 세미콜론으로 끝나지 않는 남아있을 것입니다, 마지막에 세미콜론을 기대하지 않고있는 사소한 문제가 XYZ및 ABC).

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으로 테스트되었습니다. 실제로 XYZ및 ABC예제를 사용하면 다음 과 같이 불평합니다.

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.