2012-03-02 6 views
27

W https://stackoverflow.com/a/1967183/134841 roztwór przewidziane jest statycznie sprawdzenie, czy element istnieje, ewentualnie w podklasie typu:Kontrola członkiem zachodzi w klasie podstawowej ewentualnie, C++, 11 wersja

template <typename Type> 
class has_resize_method 
{ 
    class yes { char m;}; 
    class no { yes m[2];}; 
    struct BaseMixin 
    { 
    void resize(int){} 
    }; 
    struct Base : public Type, public BaseMixin {}; 
    template <typename T, T t> class Helper{}; 
    template <typename U> 
    static no deduce(U*, Helper<void (BaseMixin::*)(), &U::foo>* = 0); 
    static yes deduce(...); 
public: 
    static const bool result = sizeof(yes) == sizeof(deduce((Base*)(0))); 
}; 

Jednak , nie działa na klasach C++ 11 final, ponieważ dziedziczy po klasie, która jest w trakcie testu, co uniemożliwia.

OTOH, to jedno:

template <typename C> 
struct has_reserve_method { 
private: 
    struct No {}; 
    struct Yes { No no[2]; }; 
    template <typename T, typename I, void(T::*)(I) > struct sfinae {}; 
    template <typename T> static No check(...); 
    template <typename T> static Yes check(sfinae<T,int, &T::reserve> *); 
    template <typename T> static Yes check(sfinae<T,size_t,&T::reserve> *); 
public: 
    static const bool value = sizeof(check<C>(0)) == sizeof(Yes) ; 
}; 

nie znajdzie sposobu, w baseclasses reserve(int/size_t).

Czy jest realizacja tego metafunkcji że zarówno znajdzie reserved() w baseclasses z T i nadal działa, jeśli T jest final?

+3

W C++ 11 można po prostu użyć "sfinae dla wyrażeń", zamiast wykonywać tę okrężną ścieżkę wymaganą w C++ 03. –

+1

@ JohannesSchaub-litb Wniosek o rozwiązanie! Chciałbym zobaczyć różnicę, jaką przynosi to tego rodzaju przykładowi. – stinky472

Odpowiedz

46

Rzeczywiście, rzeczy stały się dużo łatwiejsze w C++ 11 dzięki decltype i opcjom opóźnionego powracania.

Teraz, to tylko prostsze w użyciu metod, aby to sprawdzić:

// Culled by SFINAE if reserve does not exist or is not accessible 
template <typename T> 
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) { 
    return true; 
} 

// Used as fallback when SFINAE culls the template method 
constexpr bool has_reserve_method(...) { return false; } 

Następnie można użyć tego w klasie na przykład:

template <typename T, bool b> 
struct Reserver { 
    static void apply(T& t, size_t n) { t.reserve(n); } 
}; 

template <typename T> 
struct Reserver <T, false> { 
    static void apply(T& t, size_t n) {} 
}; 

i używasz go tak:

template <typename T> 
bool reserve(T& t, size_t n) { 
    Reserver<T, has_reserve_method(t)>::apply(t, n); 
    return has_reserve_method(t); 
} 

Lub możesz wybrać metodę enable_if:

template <typename T> 
auto reserve(T& t, size_t n) -> typename std::enable_if<has_reserve_method(t), bool>::type { 
    t.reserve(n); 
    return true; 
} 

template <typename T> 
auto reserve(T& t, size_t n) -> typename std::enable_if<not has_reserve_method(t), bool>::type { 
    return false; 
} 

Zauważ, że to przełączanie rzeczy nie jest tak proste. W ogóle, to o wiele łatwiejsze, gdy tylko SFINAE istnieje - i po prostu chcesz enable_if jeden sposób i nie dostarczają żadnych fallback:

template <typename T> 
auto reserve(T& t, size_t n) -> decltype(t.reserve(n), void()) { 
    t.reserve(n); 
} 

Jeśli substytucja nie, metoda ta jest usuwana z listy możliwych przeciążeń.

Uwaga: dzięki semantyce , (operator przecinka) można łańcuchować wiele wyrażeń w decltype i tylko ostatni faktycznie decyduje o typie. Poręczny w celu sprawdzenia wielu operacji.

+0

Czy 'T' nie musi być literałem, aby być parametrem funkcji' constexpr'? Ok, w szablonach, 'constexpr' jest upuszczane w czasie tworzenia, kiedy nie może być' constexpr', ale używasz go na liście szablonów, gdzie musi być. –

+0

@Marc: Nie, prawie nie ma ograniczeń co do tego, co może być parametrem. – Xeo

+0

OK, to dało mi właściwy kierunek. Po kolejnych uproszczeniach, dostałem go do pracy wymagającej minimalnej obsługi C++ 11 (po prostu 'decltype'), zachowując strukturę drugiej wersji C++ 98, ale zastępując wersje' Yes' z pojedynczym 'szablonem ' static decltype (static_cast (0) -> isNull(), Tak()) check (int); 'jeden. –

10

wersji, która opiera się również na decltype ale nie na przejściu dowolnych typów do (...) [który w rzeczywistości nie problem tak, patrz komentarz Johannes']:

template<typename> struct Void { typedef void type; }; 

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

template<typename T> 
struct has_reserve< 
    T 
    , typename Void< 
     decltype(std::declval<T&>().reserve(0)) 
    >::type 
>: std::true_type {}; 

Chciałbym podkreślić Zgodnie z tą cechą typ taki jak std::vector<int>& obsługuje reserve: tutaj wyrażenia są sprawdzane, a nie typy. Pytanie, na które odpowiada ta cecha brzmi: "Biorąc pod uwagę wartość l lval dla takiego typu T, dobrze sformułowano wyrażenia lval.reserve(0);". Nie jest tożsame z pytaniem "Czy ten typ lub którykolwiek z jego typów bazowych ma deklarowany element reserve".

Z drugiej strony, prawdopodobnie jest to funkcja!Pamiętaj, że nowa cecha C++ 11 ma styl is_default_constructible, , a nie. Rozróżnienie to jest subtelne, ale ma zalety. (Znajdowanie lepszej nazwy w stylu is_*ible pozostawionej jako ćwiczenie.)

W każdym razie możesz nadal korzystać z cechy, takiej jak std::is_class, aby osiągnąć pożądany efekt.

+0

Ten działa pięknie, dziękuję! – Avio