2016-05-27 14 views
14

Poszukuję sposobu utworzenia klasy z parametrem typu szablonu, opartym na numerze parametru szablonu.Jak utworzyć funkcję constexpr, która zwraca typ (do użycia w parametrze szablonu)?

Co staram się zrobić coś takiego:

template<size_t n> 
constexpr auto type_from_size() { 
    if(n < 256) { 
     return uint8_t; 
    } else { 
     return uint16_t; 
    } 
} 

template<size_t n> 
class X { 
    type_from_size<n>() t; 
} 

X<500> x; 
x.t = 500; 

Więc w kodzie powyżej, constexpr funkcja type_from_size() otrzyma numer 500 i wróci typ uint16_t, a to byłoby typ członka X.t.

Wiem, że to oczywiście straszny kod, ale czy jest to możliwe przy użyciu szablonów?

+1

Myślę, że nie można zwrócić typu. Jednak możesz "zwrócić typ" z klasami szablonów poprzez 'typedef's. – PcAF

+0

Boost.Hana powinien mieć coś do tego, ale to, czy wynik jest bardziej użyteczny, zależy od tego, co z nim zrobisz. W takim przypadku regularny szablon jest prawdopodobnie bardziej przydatny. – chris

Odpowiedz

14

Funkcja nie może zwrócić typu. Powinieneś użyć szablonu.

Do wyboru między dwoma typami wystarczy built-in std::conditional.

#include <type_traits> 
#include <cstdint> 

template <size_t n> 
using type_from_size = typename std::conditional<(n < 256), uint8_t, uint16_t>::type; 
//^if `n < 256`, the ::type member will be typedef'ed to `uint8_t`. 
//     otherwise, it will alias to `uint16_t`. 
// we then give a convenient name to it with `using`. 

template <size_t n> 
struct X { 
    type_from_size<n> t; 
    //^use the template 
}; 

Jeśli chcesz obsługiwać więcej niż dwie wartości, można zmienić wiele conditional razem jak łańcuch if/else if/else, ale OH MY EYES

template <size_t n> 
using type_from_size = 
    typename std::conditional<(n <= 0xff), uint8_t, 
     typename std::conditional<(n <= 0xffff), uint16_t, 
      typename std::conditional<(n <= 0xffffffff), uint32_t, 
       uint64_t 
      >::type 
     >::type 
    >::type; 

Można również użyć specjalizacji razem z std::enable_if (SFINAE), aby był bardziej "niskiego poziomu":

template <size_t n, typename = void> 
struct type_from_size_impl; 
// Declare a "size_t -> type" function. 
// - the `size_t n` is the input 
// - the `typename = void` is a placeholder 
// allowing us to insert the `std::enable_if` condition. 

template <size_t n> 
struct type_from_size_impl<n, typename std::enable_if<(n <= 0xff)>::type> { 
    using type = uint8_t; 
}; 
// We add a partial specialization 
// - in `std::enable_if<c>::type`, if `c` is true, `::type` will be typedef'ed to `void` 
// - otherwise, `::type` will not be defined. 
// - if `::type` is not defined, substitution failed, 
// meaning we will not select this specialization 

template <size_t n> 
struct type_from_size_impl<n, typename std::enable_if<(n > 0xff && n <= 0xffff)>::type> { 
    using type = uint16_t; 
}; 

template <size_t n> 
struct type_from_size_impl<n, typename std::enable_if<(n > 0xffff && n <= 0xffffffff)>::type> { 
    using type = uint32_t; 
}; 

template <size_t n> 
struct type_from_size_impl<n, typename std::enable_if<(n > 0xffffffff)>::type> { 
    using type = uint64_t; 
}; 

template <size_t n> 
using type_from_size = typename type_from_size_impl<n>::type; 
// Here we want to find a specialization of `type_from_size_impl<n>` 
// All 4 specializations will be tried. 
// If only one specialization works, we will use that one 
// (Which is why we need to ensure the ranges are not overlapping 
// otherwise the compiler will complain) 
// Then we take the `::type` out the complete this "type-level function". 
+1

Dziękujemy! Świetna odpowiedź. Skończyłem używać ostatniej opcji, ale +1 dla std :: conditional, której nie znałem. –

+1

Funkcje mogą zwracać typy lub raczej znaczniki typu, z którego można wyodrębnić typ. – Yakk

+0

@Yakk Biblioteka Boost 'Boost.Hana' jest doskonałym przykładem tego typu" programowania typu ". – KABoissonneault

5

Po prostu przejdziemy do przesady. Zacznij od wybieraka:

template <int I> struct choice : choice<I + 1> { }; 
template <> struct choice<10> { }; 

struct otherwise { otherwise(...) { } }; 

Następnie stwórz kaskadową serię przeciążeń, które zwracają typ. Wybór gwarantuje, że najmniejszy typ zostanie wybrany jako pierwszy, bez konieczności pisania dwustronne zakresy dla wszystkich pośrednich typów integer:

template <class T> struct tag { using type = T; } 
template <size_t N> using size_t_ = std::integral_constant<size_t, N>; 

template <size_t N, class = std::enable_if_t<(N < (1ULL << 8))>> 
constexpr tag<uint8_t> tag_from_size(size_t_<N>, choice<0>) { return {}; } 

template <size_t N, class = std::enable_if_t<(N < (1ULL << 16))>> 
constexpr tag<uint16_t> tag_from_size(size_t_<N>, choice<1>) { return {}; 

template <size_t N, class = std::enable_if_t<(N < (1ULL << 32))>> 
constexpr tag<uint32_t> tag_from_size(size_t_<N>, choice<2>) { return {}; } 

template <size_t N> 
constexpr tag<uint64_t> tag_from_size(size_t_<N>, otherwise) { return {}; } 

a następnie można napisać jednego najwyższego poziomu, który wywoła:

template <size_t N> 
using type_from_size_t = typename decltype(tag_from_size(size_t_<N>{}, choice<0>{}))::type; 

i używać go:

template <size_t N> 
class X { 
    type_from_size_t<N> t; 
}; 
+0

Dlaczego nie upuścić 'tag' i pominąć implementację funkcji? W końcu są w niedoszacowanym kontekście :) – Quentin

+0

@Quentin To zawsze działa. Nie trzeba mieć wyjątków dla innych rodzajów funkcji, które mogą wymagać wydania bezzwrotnych typów - po prostu zawsze zwracaj znacznik dla spójności. – Barry

+0

Ma sens. - – Quentin

4

Zdecydowanie. Oto bardziej elastyczny sposób robienia tego, możesz dodać tyle zakresów, ile chcesz, o ile się nie nakładają.

template <std::size_t N, class = void> 
struct TypeForSize_; 

template <std::size_t N> 
struct TypeForSize_<N, std::enable_if_t< 
    (N <= 255) 
>> { using type = std::uint8_t; }; 

template <std::size_t N> 
struct TypeForSize_<N, std::enable_if_t< 
    (N > 255 && N <= 65535) 
>> { using type = std::uint16_t; }; 

template <std::size_t N> 
using TypeForSize = typename TypeForSize_<N>::type; 

Użycie rozmiaru, dla którego nie zdefiniowano żadnego typu spowoduje błąd kompilacji.

3

Najpierw napisz static_if<bool>(A, B).

Następnie wpisz template<class T> struct tag_type{using type=T;}; i kod pomocniczy.

template<size_t n> 
constexpr auto type_from_size() { 
    return static_if< (n<256) >( 
    tag<std::uint8_t>, 
    tag<std::uint16_t> 
); 
} 

teraz zwraca inny typ znacznika w oparciu o wartość n.

Sposób użycia:

template<size_t n> 
class X { 
    typename decltype(type_from_size<n>())::type t; 
} 

lub napisać krótki alias:

template<size_t n> type_from_size_t = typename decltype(type_from_size<n>())::type; 

Oto kod dla tag_type i static_if:

template<class T>struct tag_type{using type=T;}; 
template<class T>constexpr tag_type<T> tag{}; 

template<bool b>using bool_t=std::integral_constant<bool, b>; 

template<class T, class F> 
constexpr T static_if(bool_t<true>, T t, F f) { 
    return t; 
} 
template<class T, class F> 
constexpr F static_if(bool_t<false>, T t, F f) { 
    return f; 
} 
template<bool b, class T, class F> 
constexpr auto static_if(T t, F f) { 
    return static_if(bool_t<b>, t, f); 
} 
template<bool b, class T> 
constexpr auto static_if(T t) { 
    return static_if<b>(t, [](auto&&...){}); 
} 

i zrobione.

Uwaga możemy również wykonać static_case. :)

Powiązane problemy