특정 유형 만 허용하는 C ++ 템플릿 확장하는 유형 만 허용하는 일반 클래스를 정의

Java에서는 다음과 같이 선택한 클래스를 확장하는 유형 만 허용하는 일반 클래스를 정의 할 수 있습니다.

public class ObservableList<T extends List> {
  ...
}

이것은 “extends”키워드를 사용하여 수행됩니다.

C ++에서이 키워드에 해당하는 간단한 것이 있습니까?



답변

Boost Type Traits 라이브러리 와 함께 Boost의 정적 어설트 기능을 사용하는 것이 좋습니다 is_base_of.

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

다른 간단한 경우에는 전역 템플릿을 간단히 선언 할 수 있지만 유효한 유형에 대해서만 템플릿을 정의 (명시 적으로 또는 부분적으로 전문화) 할 수 있습니다.

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[Minor EDIT 6/12/2013 : 선언되었지만 정의되지 않은 템플릿을 사용하면 컴파일러가 아니라 링커 가 발생하고 오류 메시지가 나타납니다.]


답변

다른 답변에서 언급했듯이 이것은 일반적으로 C ++에서 보증되지 않습니다. C ++에서는 “이 클래스에서 상속”이외의 다른 제약 조건을 기반으로 일반 형식을 정의하는 경향이 있습니다. 정말로 그렇게하고 싶다면 C ++ 11에서 쉽게 할 수 있습니다 <type_traits>.

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

이것은 사람들이 C ++에서 기대하는 많은 개념을 깨뜨립니다. 자신의 특성을 정의하는 것과 같은 트릭을 사용하는 것이 좋습니다. 예를 들어 observable_listtypedef가있는 모든 유형의 컨테이너와을 반환 const_iterator하는 beginend멤버 함수 를 허용하려고 할 수 const_iterator있습니다. 상속하는 클래스로 이것을 제한하면 상속 list하지 않지만 list이러한 멤버 함수 및 typedef를 제공 하는 자체 유형을 가진 사용자는 을 사용할 수 없습니다 observable_list.

이 문제에 대한 두 가지 해결책이 있습니다. 그중 하나는 아무것도 구속하지 않고 오리 타이핑에 의존하는 것입니다. 이 솔루션의 큰 단점은 사용자가 이해하기 어려운 막대한 양의 오류를 포함한다는 것입니다. 또 다른 솔루션은 인터페이스 요구 사항을 충족시키기 위해 제공된 유형을 제한하는 특성을 정의하는 것입니다. 이 솔루션의 가장 큰 단점은 추가 글쓰기를 포함하여 성가신 것으로 보일 수 있다는 것입니다. 그러나 긍정적 인 측면은 자신의 오류 메시지를 la로 작성할 수 있다는 것입니다 static_assert.

완전성을 위해 위의 예에 대한 솔루션이 제공됩니다.

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

위의 예에서 C ++ 11의 기능을 보여주는 많은 개념이 있습니다. 호기심에 대한 일부 검색어는 가변형 템플릿, SFINAE, 표현 SFINAE 및 유형 특성입니다.


답변

아직 언급하지 않은 간단한 해결책은 문제를 무시하는 것입니다. int벡터 또는 목록과 같은 컨테이너 클래스를 기대하는 함수 템플릿에서 템플릿 유형으로을 사용하려고 하면 컴파일 오류가 발생합니다. 조잡하고 단순하지만 문제를 해결합니다. 컴파일러는 지정한 유형을 사용하려고 시도하고 실패하면 컴파일 오류를 생성합니다.

그것의 유일한 문제는 당신이 얻는 오류 메시지를 읽기 까다로워 질 것입니다. 그럼에도 불구하고이 작업을 수행하는 매우 일반적인 방법입니다. 표준 라이브러리는 템플릿 유형에서 특정 동작을 예상하는 함수 또는 클래스 템플릿으로 가득 차 있으며 사용 된 유형이 유효한지 확인하지 않습니다.

더 멋진 오류 메시지를 원하거나 (컴파일러 오류를 생성하지 않지만 여전히 이해가되지 않는 사례를 포착하려는 경우) 복잡한 방법에 따라 Boost의 정적 어설 션 또는 Boost concept_check 라이브러리.

최신 컴파일러를 사용하면 built_in static_assert이 있으며 대신 사용할 수 있습니다.


답변

우리는 std::is_base_ofand 를 사용할 수 있습니다 std::enable_if:
( static_assert위의 클래스는 제거 할 수 있습니다. 우리가 참조 할 수없는 경우 사용자 정의 구현 또는 부스트 에서 사용할 수 있습니다 type_traits)

#include <type_traits>
#include <list>

class Base {};
class Derived: public Base {};

#if 0   // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
    protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
    static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base;
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;

};
#endif

int main() {
#if 0   // wrapper or base-class
    MyClass<Derived> derived;
    MyClass<Base> base;
//  error:
    MyClass<int> wrong;
#elif 1 // list-of
    MyClass<std::list<Derived>> derived;
    MyClass<std::list<Base>> base;
//  error:
    MyClass<std::list<int>> wrong;
#endif
//  all of the static_asserts if not commented out
//  or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
//  1. inner
//  2. MyClass
//  3. base + value_type
}


답변

내가 아는 한 현재 C ++에서는 불가능합니다. 그러나 새로운 C ++ 0x 표준에는 원하는 기능을 제공하는 “개념”이라는 기능을 추가 할 계획이 있습니다. C ++ Concepts에 관한 이 Wikipedia 기사 에서 자세히 설명합니다.

나는 이것이 즉각적인 문제를 해결하지는 못한다는 것을 알고 있지만 이미 새로운 표준에서 기능을 추가하기 시작한 일부 C ++ 컴파일러가 있으므로 이미 개념 기능을 구현 한 컴파일러를 찾을 수 있습니다.


답변

나는 모든 사전 답변이 나무의 숲을 보지 못했다고 생각합니다.

Java 제네릭 은 템플릿과 동일하지 않습니다 . 이들은 사용 형 소거 A는, 동적 기술 보다는 컴파일 시간 다형성 이며, 정적 기법 . 이 두 가지 매우 다른 전술이 잘 젤리 지 않는 이유는 분명해야합니다.

컴파일 타임 구문을 사용하여 런타임을 시뮬레이션하는 대신 extends실제로 수행 하는 작업을 살펴 ​​보겠습니다 . Stack OverflowWikipedia에 따르면 extends는 서브 클래 싱을 나타내는 데 사용됩니다.

C ++은 서브 클래 싱도 지원합니다.

또한 제네릭 형식으로 유형 삭제를 사용하고 유형 검사를 수행하도록 확장되는 컨테이너 클래스를 표시합니다. C ++에서는 유형 삭제 기계 장치를 직접 수행해야합니다. 이는 매우 간단합니다.

전체 클래스를 만드는 것이 아니라 사용하기 쉽도록 typedef로 감싸서 보자.

typedef std::list<superclass*> subclasses_of_superclass_only_list;

예를 들면 다음과 같습니다.

class Shape { };
class Triangle : public Shape { };

typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;

shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int's are not shapes

이제 List는 일종의 컬렉션을 나타내는 인터페이스 인 것 같습니다. C ++의 인터페이스는 단지 추상 클래스, 즉 순수한 가상 메소드 만 구현하는 클래스 일뿐입니다. 이 방법을 사용하면 개념이나 템플릿 전문화없이 C ++로 Java 예제를 쉽게 구현할 수 있습니다. 가상 테이블 조회로 인해 Java 스타일 제네릭만큼 느리게 수행되지만 종종 허용되는 손실이 될 수 있습니다.


답변

List 유형에서 파생 된 T 유형 만 허용하는 것과 같습니다.

template<typename T,
         typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
    // ...
};