Pytanie Czy istnieje coś takiego jak Templatized Case-Statement


Więc mam ten naprawdę brzydki kod:

template <typename T>
std::conditional_t<sizeof(T) == sizeof(char),
                   char,
                   conditional_t<sizeof(T) == sizeof(short),
                                 short,
                                 conditional_t<sizeof(T) == sizeof(long),
                                               long,
                                               enable_if_t<sizeof(T) == sizeof(long long),
                                                           long long>>>> foo(T bar){return reinterpret_cast<decltype(foo(bar))>(bar);}

Używam zagnieżdżonych conditional_ts aby sporządzić rodzaj spraw. Czy jest coś, co czyni to bardziej elegancko, czy też muszę przygotować własne oświadczenie na temat templatyzacji?

Uwaga: W rzeczywistości wiem, że to wykorzystanie reinterpret_cast jest zły: Dlaczego nie reinterpret_cast Force copy_n dla Casts między Same-Sized Types?


11
2018-02-24 15:08


pochodzenie


Co ten kod próbuje osiągnąć? - Slava
Możesz zrobić jeden szablon char, short, int, long long ... - amchacon
Dlaczego to jest jak języki programowania zależnego od typu zależnego? - Marcus Müller
@ Slawa Lol, próbowałem znaleźć sposób, aby to oczyścić: stackoverflow.com/a/28634468/2642059 - Jonathan Mee
@Slava: Konwertuj obiekt dowolnego typu na typ podstawowy tego samego rozmiaru (jeśli taki istnieje) zawierający te same wartości bajtów. - Mike Seymour


Odpowiedzi:


Musiałem zrobić coś takiego, więc napisałem małe opakowanie aby uzyskać efekt równomiernie. Można go użyć w następujący sposób (zob tutaj na test)

template<class T>
typename static_switch<sizeof(T)
            ,int // default case
            ,static_case<sizeof(char),char>
            ,static_case<sizeof(short),short>
            ,static_case<sizeof(long),long>
            >::type foo(T bar){ ... }

Za kulisami robi się to, co już masz, ale przez owinięcie go zachowujemy (bardziej) czytelne. Dostępna jest również wersja umożliwiająca przełączanie typu direclty T jeśli tego potrzebujesz.

Edycja: W sugestii @ Deduplicator tutaj jest kod za nim

#include <type_traits>  

/* 
 * Select a type based on the value of a compile-time constant such as a 
 * constexpr or #define using static_switch. 
 */ 

template<int I,class T> 
struct static_case { 
    static constexpr int value = I; 
    using type = T; 
}; 

template<int I, class DefaultType, class Case1, class... OtherCases> 
struct static_switch{ 
    using type = typename std::conditional< I==Case1::value ,  
                    typename Case1::type, 
                    typename static_switch<I,DefaultType,OtherCases...>::type 
                     >::type; 
}; 

struct fail_on_default {};

template<int I, class DefaultType, class LastCase> 
struct static_switch<I,DefaultType,LastCase> { 
    using type = typename std::conditional< I==LastCase::value ,  
                    typename LastCase::type, 
                    DefaultType 
                     >::type; 

    static_assert(!(std::is_same<type, fail_on_default>::value),
                  "Default case reached in static_switch!");
}; 

10
2018-02-24 15:20



Możesz zaimportować swój kod z github tutaj ..., który sprawi, że odpowiedź będzie kompletna. W każdym razie, właśnie pisałem coś takiego, ale dostałeś to. - Deduplicator
@Dan Nie jestem pewien, czy rozumiem, jak to zastosować. Jeśli możesz go podłączyć, test powinien być dość prosty: auto val = foo(13.0);static_assert(is_same<long long, decltype(val)>::value); - Jonathan Mee
@ JonathanMee: Na marginesie można go uprościć, jeśli wartość można uzyskać na podstawie typu, jak w twoim przypadku. - Deduplicator
@ JonathanMee Wydaje się działać, zobacz edycję. - Dan
@Deduplicator Po prostu rozwiązałem problem static_switch Prace. Co masz na myśli: "Można to uprościć?" - Jonathan Mee


Wersja szablonu instrukcji switch jest wyspecjalizowanym szablonem.

template<size_t n> struct matching_type;
template<> struct matching_type<sizeof(char)> { typedef char type; };
template<> struct matching_type<sizeof(short)> { typedef short type; };
template<> struct matching_type<sizeof(int)> { typedef int type; };
template<> struct matching_type<sizeof(long)> { typedef long type; };
template<> struct matching_type<sizeof(long long)> { typedef long long type; };

template<typename T>
matching_type<sizeof(T)>::type foo(T bar)
{
    return reinterpret_cast<decltype(foo(bar))>(bar);
}

4
2018-02-24 15:20



Miejmy nadzieję, że OP usunie wcześniej wszystkie typy o równych rozmiarach ... - Deduplicator
Zauważ, że w niektórych systemach sizeof(int) może być równe sizeof(long) a ten kod nie zostanie skompilowany - borisbn
To prawda, ale switch oświadczenia mają ten sam problem! - Raymond Chen
@borisbn Prawdą jest również, że spowodowałoby to błąd podczas kompilacji, z nadzieją, że jestem na tyle inteligentny, aby sobie z nim poradzić? - Jonathan Mee
@Deduplicator: Czy nie byłoby bardziej skuteczne komentowanie tego pytania, zamiast rozsyłania komentarzy na temat odpowiedzi, które odpowiedziały na pytanie w miarę, jak było zadawane? - IInspectable


Tak długo, jak rozumiesz ryzyko, że ten sam rozmiar może nie być zamiennikiem, możesz po prostu wstawić a mpl::map..

typedef map<
      pair<int_<sizeof(char)>, char>,
      pair<int_<sizeof(short)>, short>,
      pair<int_<sizeof(int)>, int>,
      pair<int_<sizeof(long long)>, long long>
    > m;

na przykład

#include <algorithm>
#include <iostream>

#include <boost/mpl/at.hpp>
#include <boost/mpl/map.hpp>

using namespace boost::mpl;

typedef map<
      pair<int_<sizeof(char)>, char>,
      pair<int_<sizeof(short)>, short>,
      pair<int_<sizeof(int)>, int>,
      pair<int_<sizeof(long long)>, long long>
    > m;

template <typename T>
typename at<m, int_<sizeof(T)>>::type foo(T bar)
{ return reinterpret_cast<decltype(foo(bar))>(bar); }


struct doh
{
    std::string a, b, c;
};

int main()
{
    {
      char c;
      static_assert(std::is_same<decltype(foo(c)), char>::value, "error");
    }
    {
      short c;
      static_assert(std::is_same<decltype(foo(c)), short>::value, "error");
    }
    {
      int c;
      static_assert(std::is_same<decltype(foo(c)), int>::value, "error");
    }
    {
      long long c;
      static_assert(std::is_same<decltype(foo(c)), long long>::value, "error");
    }
    {
      double c;
      static_assert(std::is_same<decltype(foo(c)), long long>::value, "error");
    }    
    {
      doh c;
      static_assert(std::is_same<decltype(foo(c)), void_>::value, "error");
    }    
}

3
2018-02-24 15:30



To wygląda obiecujące rozwiązanie. Ale nie mam Boost. Zastanawiam się, czy jest to coś, co można osiągnąć za pomocą std::map jakoś? - Jonathan Mee
@ JonathanMee, nie jest możliwe z std::map, możesz to zrobić za pomocą switch, ale byłby to raczej test w czasie wykonywania niż kompilacja ... - Nim


Może coś takiego:

template <size_t N> struct SuitablySized;

template<> struct SuitablySized<sizeof(char)> {
  typedef char type;
};
template<> struct SuitablySized<sizeof(short)> {
  typedef short type;
};
// Add more cases to taste

template <typename T>
typename SuitablySized<sizeof(T)>::type foo(T bar);

2
2018-02-24 15:20



Miejmy nadzieję, że OP usunie wcześniej wszystkie typy o równych rozmiarach ... - Deduplicator


Znacznik typu:

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

void_t (wchodząc w C ++ 17 do kompilatora blisko ciebie):

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

enable_first_t bierze paczkę std::enable_if (zauważ brak _t) i zwraca pierwszą, która przejdzie test. Możesz użyć tag<X> zamienić std::enable_if<true, X>:

template<class T,class=void>struct has_type:std::false_type{};
template<class T>struct has_type<T, void_t<typename T::type>>:std::true_type{};

namespace details {
  template<class, class...Ts>
  struct enable_first {};
  template<class T0, class...Ts>
  struct enable_first<std::enable_if_t< !has_type<T0>{} >, T0, Ts... >:enable_first<void, Ts...> {};
  template<class T0, class...Ts>
  struct enable_first<std::enable_if_t<  has_type<T0>{} >, T0, Ts...>:T0 {};
}

template<class...Ts>using enable_first_t=typename details::enable_first<void, Ts...>::type;

template<class T>
using result = enable_first_t<
  std::enable_if<sizeof(T)==sizeof(char), char>,
  std::enable_if<sizeof(T)==sizeof(short), short>,
  std::enable_if<sizeof(T)==sizeof(long), long>,
  tag<int> // default
>;

to zachowuje się bardzo jak switch, ale instrukcje są pełnymi wyrażeniami boolowskimi.

przykład na żywo


1
2018-02-24 15:40



Dlaczego nie template<class...> using void_t = void;? - dyp