2016-01-06 17 views
7

mogę być typu tak:warunkowego definicja typu alias

template<typename T> 
struct wrapper 
{ 
    using foo = typename T::foo; 
    using bar = typename T::bar; 
    using baz = typename T::baz; 
    // More of those... 
}; 

ja jak foo, bar, baz i równoważne typu aliasy być określone tylko wtedy, gdy odpowiednik typu istnieje T. Rozwiązania używające std::conditional pozwalają zastąpić je czymś innym, gdy nie istnieje, ale nie wiem, jak upewnić się, że nie istnieje, gdy odpowiedni typ nie istnieje w typie szablonu. Powyższy kod powoduje błąd podczas tworzenia instancji wrapper<T>, jeśli T nie definiuje jednego z aliasów typów.

nie mogę zrobić wrapper Dziedzicz T ponieważ wrapper nie ma robić wszystko T może zrobić. Ponadto, zastosowanie częściowej specjalizacji prowadziłoby do pewnego rodzaju eksplozji eksplozji i szybko stałoby się nieosiągalne. Pewnie mógłby foo, bar ... szablonów aliasy typu do wstrzykiwania std::enable_if w parametrze szablonu domyślnego, ale potem użytkownicy będą musieli napisać wrapper<T>::foo<>, wrapper<T>::bar<> zamiast wrapper<T>::foo, wrapper<T>::bar, itp ... i nie chcę tego.

Czy istnieje prosty, ale obsługiwany sposób definiowania takiego aliasu typów tylko wtedy, gdy odpowiedni alias typu istnieje w T?

Odpowiedz

8

Można zdefiniować check_foo, check_bar i check_baz cechy, które dopiero mają typ, jeśli istnieje, to dziedziczą wszystkie z nich w wrapper:

template <typename T, typename=void> 
struct check_foo{}; 

template <typename T> 
struct check_foo<T, void_t<typename T::foo>> { 
    using foo = typename T::foo; 
}; 

// ditto for bar, baz, etc. 

template <typename T> 
struct wrapper : 
    check_foo<T>, 
    check_bar<T>, 
    check_baz<T> 
{ }; 

To jeden dodatkowy struct od rodzaju, ale na pewno lepsze niż wykładnicza wersja, o której wspomniałeś. Można nawet zrobić to makro jeśli były odpowiednio przewrotny:

#define DEFINE_CHECKER(NAME) \ 
    template <typename T, typename=void> struct check_##NAME{}; \ 
    template <typename T> struct check_##NAME<T,void_t<typename T::NAME>> \ 
    { using NAME = typename T::NAME; }; 

DEFINE_CHECKER(foo) 
DEFINE_CHECKER(bar) 
DEFINE_CHECKER(baz) 

Straszne, wiem, ale myślę, że trzeba zapłacić tę cenę, jeśli naprawdę chcesz wrapper<T>::bar zamiast wrapper<T>::bar<>. Jeśli używasz wersji makro, dodanie nowego typu oznaczałoby tylko nowe DEFINE_CHECKER(newname) i dodanie check_newname<T> do listy dziedziczenia opakowania. Mogło być gorzej.

Live Demo

+1

To było rozwiązanie, które miałem na myśli i byłem gotowy na wypróbowanie go. To nie jest ładne, ale jeszcze bardziej możliwe do utrzymania niż inne rozwiązania bez narażania użytkowników na niepotrzebne rzeczy. Dzięki :) – Morwenn

+0

Dang. Miałem nadzieję, że będzie lepsze rozwiązanie niż to. – Justin

5

Należy zauważyć, że odpowiedź za pomocą void_t przez @TartanLlama jest w porządku, jak to jest. Jednak w C++ 17 najprawdopodobniej będzie kilka pomocników biblioteki standardowej, takich jak is_detected_v, które wykonają połączenia pod numerem void_t pod maską.

#include <experimental/type_traits> 

// helpers to reduce boilerplate 
template<class Tag> 
struct empty_base {}; 

template<template<class> class Holder, template<class> class Op, class Arg> 
using inject_or_t = std::conditional_t 
< 
    std::experimental::is_detected_v<Op, Arg>, 
    Holder<Arg>, 
    empty_base<Op<Arg>> 
>; 

// add detector + holder for every conditional nested type 

template<class T> 
using foo_t = typename T::foo; 

template<class T> 
struct foo_holder { using foo = foo_t<T>; }; 

template<class T> 
using bar_t = typename T::bar; 

template<class T> 
struct bar_holder { using bar = bar_t<T>; }; 

template<class T> 
using baz_t = typename T::baz; 

template<class T> 
struct baz_holder { using baz = baz_t<T>; }; 

// wrapper is now simply: 

template<class T> 
struct wrapper 
: inject_or_t<foo_holder, foo_t, T> 
, inject_or_t<bar_holder, bar_t, T> 
, inject_or_t<baz_holder, baz_t, T> 
{}; 

struct Test 
{ 
    using foo = int; 
    using bar = int; 
    using baz = int; 
}; 

int main() 
{ 
    static_assert(!std::experimental::is_detected_v<foo_t, wrapper<int>>); 
    static_assert(!std::experimental::is_detected_v<bar_t, wrapper<int>>); 
    static_assert(!std::experimental::is_detected_v<baz_t, wrapper<int>>); 

    static_assert(std::experimental::is_detected_v<foo_t, wrapper<Test>>); 
    static_assert(std::experimental::is_detected_v<bar_t, wrapper<Test>>); 
    static_assert(std::experimental::is_detected_v<baz_t, wrapper<Test>>); 
} 

Live Example Zauważ, że jego jest jednym z bardzo nielicznych przykładów gdzie libstdC++ 6.0 SVN bagażnik może (obecnie!) Zrobić coś, libC++ 3.9 SVN bagażnik nie może.

Wymaga to dodania aliasu detektora i struktury uchwytów dla każdego typu wtryskiwanego i całkowicie eliminuje konieczność stosowania makr.

+0

Naprawdę eleganckie rozwiązanie!Oczywiście można to zrobić w C++ 14, implementując odpowiednich pomocników lub rażąco je wykradając, jak [this] (http://coliru.stacked-crooked.com/a/cee230048c1a807c). – TartanLlama

+0

Och, teraz to interesujące rozwiązanie: o – Morwenn

+0

@TemplateRex Chociaż nie o to prosił @Morwenn, prawda? 'wrapper ' próbuje po prostu odziedziczyć po 'int' trzykrotnie zamiast dziedziczyć aliasy" foo', 'bar' i' baz'. – TartanLlama