2016-08-28 7 views
14

최근 Java에서 많은 프로그래밍을 해왔습니다. 이제는 C++ 루틴으로 되돌아갑니다 (실제로 포인터와 세그먼트 오류가 누락되기 시작했습니다). C++가 템플릿을 광범위하게 지원한다는 것을 알고 있다면 일반화 된 코드 작성에 유용 할 수있는 Java의 기능을 가지고 있는지 궁금합니다. 두 개의 클래스 그룹이 있습니다. 그 중 하나에는 first() 메서드가 있고 다른 메서드에는 second() 메서드가 있습니다. 클래스가 소유하고있는 메소드에 따라 컴파일러가 선택한 템플릿을 전문화하는 방법이 있습니까?메소드를 기반으로하는 특수 템플릿

public class Main { 
    public static void main(String[] args) { 
     First first =() -> System.out.println("first"); 
     Second second =() -> System.out.println("second"); 
     method(first); 
     method(second); 
    } 

    static <T extends First> void method(T argument) { 
     argument.first(); 
    } 

    static <T extends Second> void method(T argument) { 
     argument.second(); 
    } 
} 

경우 FirstSecond는 인터페이스입니다 : 나는 자바의 하나와 유사한 행동을 목표로하고있다. 상위 클래스에서 각 그룹을 파생시킴으로써이 두 그룹을 그룹화 할 수 있지만, 항상 가능하지는 않습니다 (C++에서는 autoboxing이없고 일부 클래스는 공통 조상으로부터 상속받지 않음).

좋은 예는 STL 라이브러리입니다. 일부 클래스는 push()과 같은 메서드를 사용하고 다른 일부 메서드는또는 push_back()입니다. 여러 변수를 사용하여 여러 값을 컨테이너에 삽입해야하는 함수를 만들고 싶습니다. Java에서는 콜렉션에 공통 조상이 있으므로 쉽게 수행 할 수 있습니다. 반면에 C++에서는 항상 그런 것은 아닙니다. 나는 오리 입력하여 그것을 시도하지만, 컴파일러는 오류 메시지를 얻을 :

template <typename T> 
void generic_fcn(T argument) { 
    argument.first(); 
} 

template <typename T> 
void generic_fcn(T argument) { 
    argument.second(); 
} 

그래서 내 질문은 : 모든 단일 사건을 전문으로 불필요한 boileplate 코드를 만들지 않고 가능한 그런 행동을 구현되어 있습니까?

+1

당신은 다른 템플릿 매개 변수로 정책 클래스를 사용하고, 그것으로부터 상속 할 수 있습니다. 그런 다음 여러 컨테이너 유형에 대한 정책 구현이 있습니다. 또한 [정책 기반 디자인] (https://en.wikipedia.org/wiki/Policy-based_design)을 참조하십시오. –

+0

이것이 어떻게 문제를 해결할 수 있는지 예를 보여줄 수 있습니까? 시도할만한 가치가있는 것 같기 때문에. –

+0

wikipedia 기사에 샘플이 있습니다.이 단어를 검색하면 더 많은 것을 찾을 수 있습니다. –

답변

16

<T extends First> 대신 sfinae라고하는 것을 사용할 것입니다. 이것은 매개 변수 유형을 기반으로 함수에 constaints를 추가하는 기술입니다. 여기

는 C++로 그것을 할 거라고 방법은 다음과 같습니다

template <typename T> 
auto generic_fcn(T argument) -> void_t<decltype(argument.first())> { 
    argument.first(); 
} 

template <typename T> 
auto generic_fcn(T argument) -> void_t<decltype(argument.second())> { 
    argument.second(); 
} 

함수가 존재하는 경우, 컴파일러는 argument.second()의 종류 나 argument.first()의 종류가 필요합니다. 표현식이 형식을 산출하지 않으면 (예 : Tfirst() 기능이 없음) 컴파일러는 또 다른 오버로드를 시도합니다.

void_t

는 다음과 같이 구현됩니다

template<typename...> 
using void_t = void; 

또 다른 좋은 점은 당신이 그런 클래스가있는 경우이다 :

struct Bummer { 
    void first() {} 
    void second() {} 
}; 

그런 다음 컴파일러 효과적으로 당신을 말할 것이다을 호출이 모호하다는 타입 때문에 두 제약 조건 모두 일치.


당신이 정말로 당신은이 주제에 대한 자세한 내용을 읽으려면 유형의 특성 std::is_base_of

template <typename T> 
auto generic_fcn(T argument) -> std::enable_if_t<std::is_base_of<First, T>::value> { 
    argument.first(); 
} 

template <typename T> 
auto generic_fcn(T argument) -> std::enable_if_t<std::is_base_of<Second, T>::value> { 
    argument.second(); 
} 

을 사용할 수 있습니다 (C++에서이 같은 일이 또는 구현) 유형이 다른를 확장하는 경우 테스트하려면 , cpprefence에서 sfinae을 확인하고 표준 라이브러리에서 제공하는 available traits을 확인할 수 있습니다.그것은 다음과 같이

4

당신은 전화를 전달할 수 있습니다 클래스가 둘 다있는 경우

#include<utility> 
#include<iostream> 

struct S { 
    template<typename T> 
    auto func(int) -> decltype(std::declval<T>().first(), void()) 
    { std::cout << "first" << std::endl; } 

    template<typename T> 
    auto func(char) -> decltype(std::declval<T>().second(), void()) 
    { std::cout << "second" << std::endl; } 

    template<typename T> 
    auto func() { return func<T>(0); } 
}; 

struct First { 
    void first() {} 
}; 

struct Second { 
    void second() {} 
}; 

int main() { 
    S s; 
    s.func<First>(); 
    s.func<Second>(); 
} 

방법 firstsecond 선호한다.
그렇지 않으면 func은 함수 오버로드를 사용하여 두 메서드를 테스트하고 올바른 메서드를 선택합니다.
이 기술은 sfinae이라고하며이 이름을 사용하여 웹에서 자세한 내용을 검색합니다.

5

많은 옵션이 C++에서 제공됩니다.

자유로운 함수를 선호하고 결과 유형을 올바르게 반환하는 것이 좋습니다.

#include <utility> 
#include <type_traits> 
#include <iostream> 

struct X 
{ 
    int first() { return 1; } 
}; 

struct Y 
{ 
    double second() { return 2.2; } 
}; 


// 
// option 1 - specific overloads 
// 

decltype(auto) generic_function(X& x) { return x.first(); } 
decltype(auto) generic_function(Y& y) { return y.second(); } 

// 
// option 2 - enable_if 
// 

namespace detail { 
    template<class T> struct has_member_first 
    { 
    template<class U> static auto test(U*p) -> decltype(p->first(), void(), std::true_type()); 
    static auto test(...) -> decltype(std::false_type()); 
    using type = decltype(test(static_cast<T*>(nullptr))); 
    }; 
} 
template<class T> using has_member_first = typename detail::has_member_first<T>::type; 

namespace detail { 
    template<class T> struct has_member_second 
    { 
    template<class U> static auto test(U*p) -> decltype(p->second(), void(), std::true_type()); 
    static auto test(...) -> decltype(std::false_type()); 
    using type = decltype(test(static_cast<T*>(nullptr))); 
    }; 
} 
template<class T> using has_member_second = typename detail::has_member_second<T>::type; 

template<class T, std::enable_if_t<has_member_first<T>::value>* =nullptr> 
decltype(auto) generic_func2(T& t) 
{ 
    return t.first(); 
} 

template<class T, std::enable_if_t<has_member_second<T>::value>* =nullptr> 
decltype(auto) generic_func2(T& t) 
{ 
    return t.second(); 
} 

// 
// option 3 - SFNAE with simple decltype 
// 

template<class T> 
auto generic_func3(T&t) -> decltype(t.first()) 
{ 
    return t.first(); 
} 

template<class T> 
auto generic_func3(T&t) -> decltype(t.second()) 
{ 
    return t.second(); 
} 


int main() 
{ 
    X x; 
    Y y; 

    std::cout << generic_function(x) << std::endl; 
    std::cout << generic_function(y) << std::endl; 

    std::cout << generic_func2(x) << std::endl; 
    std::cout << generic_func2(y) << std::endl; 

    std::cout << generic_func3(x) << std::endl; 
    std::cout << generic_func3(y) << std::endl; 

} 
3

다음은 회원이 존재하는지 확인하는 데 도움이되는 작은 라이브러리입니다.

namespace details { 
    template<template<class...>class Z, class always_void, class...> 
    struct can_apply:std::false_type{}; 
    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{}; 
} 
template<template<class...>class Z, class...Ts> 
using can_apply=details::can_apply<Z, void, Ts...>; 

지금 우리가 쓸 수있는 첫 번째 및 제 2 쉽게 있습니다 second에 대한 유사

template<class T> 
using first_result = decltype(std::declval<T>().first()); 
template<class T> 
using has_first = can_apply<first_result, T>; 

하고 있습니다.

이제 우리의 방법이 있습니다. 우리는 첫째 또는 둘째를 부르고 싶습니다.

template<class T> 
void method_second(T& t, std::true_type has_second) { 
    t.second(); 
} 
template<class T> 
void method_first(T& t, std::false_type has_first) = delete; // error message 
template<class T> 
void method_first(T& t, std::true_type has_first) { 
    t.first(); 
} 
template<class T> 
void method_first(T& t, std::false_type has_first) { 
    method_second(t, has_second<T&>{}); 
} 
template<class T> 
void method(T& t) { 
    method_first(t, has_first<T&>{}); 
} 

이것은 태그 디스패치로 알려져 있습니다.

method.first()으로 T&을 호출 할 수 있는지를 결정하는 method_first을 호출합니다. 가능한 경우 호출하는 호출 .first().

전달할 수없는 경우 method_second으로 전달하고 .second()이 있는지 테스트합니다.

둘 다 가지지 않으면 컴파일시 오류 메시지를 생성하는 =delete 함수를 호출합니다.

이렇게 많은 방법이 많이 있습니다. 개인적으로 SFIANE가 생성하는 것보다 일치 오류가 더 좋은 오류 메시지를 얻을 수 있기 때문에 태그를 파견하는 것이 개인적으로 좋아합니다.

C++ 17에서는 더 직접적인 될 수 있습니다

template<class T> 
void method(T & t) { 
    if constexpr (has_first<T&>{}) { 
    t.first(); 
    } 
    if constexpr (has_second<T&>{}) { 
    t.second(); 
    } 
}