C ++에서 자율적`self` 멤버 유형을 구현할 수 있습니까? 버그를 일으킬 수 있습니다. decltype이

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동일한 작업을 수행하지만 후기 바인딩을 사용 하는)에 대해서는 걱정하지 않을 것 입니다.



답변

다음은 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에서 작동하는지 확인했습니다.


답변

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

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완료해야합니다.


답변

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

#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) {
};

답변

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

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

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

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


답변

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) 여야하며 […]

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


답변

#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테스트 대상입니다.


답변

나는 또한 불가능하다고 생각합니다. 여기에 또 다른 실패했지만 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에서 주소를 가져오고 싶을 때 클래스 로 자격을 부여해야하기 때문에 실패합니다.