7

Ho definito un tipo che funge da numero intero. Voglio definire una specializzazione per std :: common_type per il mio tipo. Tuttavia, questa specializzazione dovrebbe essere in grado di fornire il common_type di bounded_integer (my class) in combinazione con qualsiasi numero di altri argomenti che sono o altri tipi di bounded_integer o built-in. Voglio che il seguente codice sia valido:Specializzazione parziale del modello di classe per un tipo visualizzato in qualsiasi posizione di un parametro del parametro modello variadiale

std::common_type<bounded_integer<1, 10>>::type 
std::common_type<bounded_integer<1, 10>, int>::type 
std::common_type<int, long, bounded_integer<1, 10>>::type 
std::common_type<int, int, long, short, long long, short, bounded_integer<1, 10>, int, short, short, short, ..., short, bounded_integer<1, 10>>::type 

Il mio primo tentativo di risolvere questo problema è stato utilizzando enable_if. Tuttavia, mi sono reso conto che questo non mi permetteva di distinguere dalla definizione biblioteca di common_type, come quello che ho avuto è stato essenzialmente

#include <type_traits> 

class C {}; 

template<typename T, typename... Ts> 
class contains_c { 
public: 
     static constexpr bool value = contains_c<T>::value or contains_c<Ts...>::value; 
}; 
template<typename T> 
class contains_c<T> { 
public: 
     static constexpr bool value = std::is_same<T, C>::value; 
}; 

namespace std { 

template<typename... Args, typename std::enable_if<contains_c<Args...>::value>::type> 
class common_type<Args...> { 
public: 
     using type = C; 
}; 

}  // namespace std 

int main() { 
} 

Qualora la 'specializzazione parziale' è in realtà solo "argomenti", che non c'è più specializzato di quello che abbiamo.

Così sembra che l'unica soluzione è quella di richiedere i miei utenti di fare una delle seguenti:

  1. mettere sempre la bounded_integer come primo argomento a common_type
  2. utilizzare sempre il mio make_bounded (built-in valore intero) per convertire i loro interi in bounded_integer (quindi non avere una specializzazione di common_type per tipi built-in in combinazione con bounded_integer)
  3. mai mettere bounded_integer in una posizione maggiore di N, dove N è un numero determinato da I , simile al vecchio modello variadic di Visual Studio, attorno allo

3 sarebbe simile a questa:

// all_bounded_integer_or_integral and all_are_integral defined elsewhere with obvious definitions 
template<intmax_t minimum, intmax_t maximum, typename... Ts, typename = type std::enable_if<all_bounded_integer_or_integral<Ts...>::value>::type> 
class common_type<bounded_integer<minimum, maximum>, Ts...> { 
}; 
template<typename T1, intmax_t minimum, intmax_t maximum, typename... Ts, typename = typename std::enable_if<all_are_integral<T1>::value>::type, typename = typename std::enable_if<all_bounded_integer_or_builtin<Ts...>::value>::type> 
class common_type<T1, bounded_integer<minimum, maximum>, Ts...> { 
}; 
template<typename T1, typename T2, intmax_t minimum, intmax_t maximum, typename... Ts, typename = typename std::enable_if<all_are_integral<T1, T2>::value>::type, typename = typename std::enable_if<all_bounded_integer_or_builtin<Ts...>::value>::type> 
class common_type<T1, T2, bounded_integer<minimum, maximum>, Ts...> { 
}; 
// etc. 

c'è un modo migliore per ottenere questo risultato (modello di specializzazione, quando tutti i tipi si incontrano una condizione e uno dei tipi si incontrano un'altra condizione) per una classe che non posso cambiare la definizione originale per?

EDIT:

Sulla base delle risposte, non sono stato abbastanza chiaro nel mio problema.

In primo luogo, il comportamento atteso:

Se qualcuno chiama std :: common_type con tutti i tipi di essere un esempio di bounded_integer o di un built-in di tipo numerico, voglio che il risultato sia un bounded_integer che ha un minimo di tutti i minimi possibili e il massimo di tutti i massimi possibili.

Il problema:

ho una soluzione di lavoro quando qualcuno chiama std :: common_type su qualsiasi numero di bounded_integer. Tuttavia, se mi specializzo solo la versione a due argomenti, poi mi imbatto nel seguente problema:

std::common_type<int, unsigned, bounded_integer<0, std::numeric_limits<unsigned>::max() + 1>

dovrebbe darmi

bounded_integer<std::numeric_limits<int>::min(), std::numeric_limits<unsigned>::max() + 1>

Tuttavia, non è così.Prima applica common_type a int e unsigned, che segue le regole di promozione integrale standard, fornendo unsigned. Poi si restituisce il risultato di common_type con unsigned e il mio bounded_integer, dando

bounded_integer<0, std::numeric_limits<unsigned>::max() + 1>

Così aggiungendo unsigned alla metà del parametro pacchetto, anche se dovrebbe avere assolutamente alcun impatto sul tipo di risultato (le sue gamme sono interamente contenuti negli intervalli di tutti gli altri tipi), ma influenza ancora il risultato. L'unico modo che posso pensare per evitare questo è di specializzarsi su std::common_type per un numero qualsiasi di numeri interi integrati seguiti da bounded_integer, seguiti da un numero qualsiasi di numeri interi incorporati o da bounded_integer.

La mia domanda è: come posso fare questo senza doverlo approssimare scrivendo manualmente un numero arbitrario di parametri seguito da un bounded_integer seguito da un pacchetto di parametri, o non è possibile?

EDIT 2:

La ragione per cui common_type darà valori errati può essere spiegato da questo ragionamento secondo lo standard (citando N3337)

La common_type di int e unsigned è unsigned. Per un esempio: http://ideone.com/9IxKIW. Standardese si trovano in § 20.9.7.6/3, dove il common_type dei due valori è

typedef decltype(true ? declval<T>() : declval<U>()) type;

In § 5.16/6, si dice

il secondo e terzo operandi hanno aritmetica o tipo di enumerazione; vengono eseguite le normali conversioni aritmetiche per portarle a un tipo comune e il risultato è di quel tipo.

Le conversioni aritmetiche abituali sono definiti nel § 5/9 come

Altrimenti, se l'operando che ha senza segno tipo intero ha rango maggiore o uguale al rango del tipo dell'altro operando, l'operando con tipo intero con segno deve essere convertito nel tipo di l'operando con tipo intero senza segno.

+0

cosa dovrebbe 'std :: common_type , short> :: type' output? 'short' o' ranged_integer :: min(), std :: numeric_limits :: max()> '. – brunocodutra

+0

@brunocodutra: ho aggiornato il mio post per spiegare. Mi aspetto che il risultato di 'common_type' di qualsiasi espressione che contiene tutti i tipi di' ranged_integer' e integrale sia un 'ranged_integer' con un intervallo che contenga tutti i valori possibili di qualsiasi tipo. –

risposta

3

std::common_type estrapola la propria specializzazione a due argomenti nel caso n-argument. Hai solo bisogno di specializzare i casi a due argomenti.

template< typename other, int low, int high > 
struct common_type< other, ::my::ranged_integer< low, high > > { 
    using type = other; 
}; 

template< typename other, int low, int high > 
struct common_type< ::my::ranged_integer< low, high >, other > { 
    using type = other; 
}; 

template< int low, int high > 
struct common_type< ::my::ranged_integer< low, high >, 
        ::my::ranged_integer< low, high > > { 
    using type = ::my::ranged_integer< low, high >; 
}; 

questo lascia undefined il common_type tra i diversi variava numeri interi. Suppongo che potresti farlo con min e max.

È possibile anche creare un tratto is_ranged_integer se la classe supporta l'ereditarietà.

Non dimenticare di inserire la libreria in uno spazio dei nomi.

+0

+1 riferimento standard: [meta.trans.other]/3. – dyp

+0

@Potatoswatter Credo che DyP si riferisca al supplementare trailing ':: type>'. – brunocodutra

+0

@brunocodutra grazie, non l'ho nemmeno visto: P – Potatoswatter

0

forse mi manca qualcosa, ma non vuoi proprio qualcosa di simile per il caso int:

namespace std { 

// first give ranged_integer a ground zero 
    template<intmax_t minimum, intmax_t maximum> 
    class common_type<ranged_integer<minimum, maximum>> { 
     typedef typename ranged_integer<minimum, maximum> type; 
    }; 

// sort out int 
    template<intmax_t minimum, intmax_t maximum> 
    class common_type<int, ranged_integer<minimum, maximum>> { 
     typedef typename ranged_integer<minimum, maximum> type; 
    }; 

    template<intmax_t minimum, intmax_t maximum> 
    class common_type<ranged_integer<minimum, maximum>, int>> { 
     typedef typename ranged_integer<minimum, maximum> type; 
    }; 

ripetere questo per `long, long long, ecc ... e common_type 's la definizione del modello si prenderà cura di tutti i casi variadici con un solo tipo ranged_integer?

1

Ecco una possibile implementazione: risposta

#include <limits> 
#include <utility> 
#include <iostream> 

template<typename T, typename U> 
static constexpr auto min(T x, U y) -> decltype(x < y ? x : y) 
{ 
    return x < y ? x : y; 
} 

template<typename T, typename U> 
static constexpr auto max(T x, U y) -> decltype(x < y ? x : y) 
{ 
    return x > y ? x : y; 
} 

template<intmax_t f, intmax_t l> 
struct ranged_integer 
{ 
    static intmax_t const first = f; 
    static intmax_t const last = l; 
    static_assert(l > f, "invalid range"); 
}; 

template <class ...T> struct common_type 
{ 
}; 

template <class T> 
struct common_type<T> 
{ 
    typedef T type; 
}; 

template <class T, class U> 
struct common_type<T, U> 
{ 
    typedef decltype(true ? std::declval<T>() : std::declval<U>()) type; 
}; 

template <class T, intmax_t f, intmax_t l> 
struct common_type<T, ranged_integer<f,l>> 
{ 
    typedef ranged_integer< min(std::numeric_limits<T>::min(),f) , max(std::numeric_limits<T>::max(),l) > type; 
}; 

template <class T, intmax_t f, intmax_t l> 
struct common_type<ranged_integer<f,l>, T> 
{ 
    typedef typename common_type<T, ranged_integer<f,l>>::type type; 
}; 

template <intmax_t f1, intmax_t l1, intmax_t f2, intmax_t l2> 
struct common_type<ranged_integer<f1,l1>, ranged_integer<f2,l2>> 
{ 
    typedef ranged_integer< min(f1,f2) , max(l1,l2) > type; 
}; 

template <class T, class U, class... V> 
struct common_type<T, U, V...> 
{ 
    typedef typename common_type<typename common_type<T, U>::type, V...>::type type; 
}; 

int main(int argc, char *argv[]) 
{ 
    typedef common_type<char, ranged_integer<-99999999, 20>, short, ranged_integer<10, 999999999>, char>::type type; 
    std::cout << type::first << std::endl; // -99999999 
    std::cout << type::last << std::endl; // 999999999 
    return 0; 
} 
2

Breve

Se si utilizza std::common_type come previsto dalla libreria standard è assolutamente necessario, non c'è modo migliore diverso dai 3 alternative voi stessi osservati. Se un utente definito common_type è considerato accettabile, è possibile ottenere ciò che si desidera come mostrato di seguito.


lungo risposta

Hai ragione quando dici std::common_type<unsigned [long [long]] int, [long [long]] int>::type produrrà unsigned [long [long]]. Tuttavia, poiché la common_ type di qualsiasi espressione contenente ranged_integer è esso stesso una ranged_integer e dato che specializzazioni coinvolgono ranged_integer dedurre correttamente le gamme, c'è solo un problema se il coppie common_type dei tipi precedenti [long [long]] unsigned produce [long [long]] int .che ci lascia solo sei casi abbiamo Alla soluzione, ̶ Ovvero ̶ ̶s̶t̶d̶:̶:̶c̶o̶m̶m̶o̶n̶_̶t̶y̶p̶e̶<̶u̶n̶s̶i̶g̶n̶e̶d̶ ̶[̶l̶o̶n̶g̶ ̶[̶l̶o̶n̶g̶]̶]̶ ̶i̶n̶t̶,̶ ̶[̶l̶o̶n̶g̶ ̶[̶l̶o̶n̶g̶]̶]̶ ̶i̶n̶t̶>̶:̶:̶t̶y̶p̶e̶ ̶ e loro ordinamento ̶p̶e̶r̶m̶u̶t̶a̶t̶i̶o̶n̶s̶.̶ ̶ (Sto ignorando la larghezza fissa tipi qui, ̶ ma estendentesi L'idea di considerarli dovrebbe essere str a̶i̶g̶h̶t̶f̶o̶r̶w̶a̶r̶d̶) ̶

possiamo ottenere che da ancora fornendo esplicito specializzazioni: ̶

Infatti non possiamo secondon3485

[meta.type.synop] Il paragrafo 1

"Il comportamento di un programma che aggiunge uno speciale le istanze per qualsiasi modello di classe definito in questo sottopunto [template <class... T> common_type incluso] non è definito se non diversamente specificato. "

[meta.trans.other] Tabella 57

[...] Un programma può specializzarsi questo tratto [template <class... T> common_type] se almeno un parametro di modello nella specializzazione è un utente-definito genere. [...] "

Ciò implica che non c'è modo valido di sovrascrivere il comportamento per std::common_type<unsigned [long [long]] int, [long [long]] int>::type, esso è richiesto dalla norma di cedere sempre unsigned [long [long]] int come sottolineato prima.


alternativa a std::common_type

Un'alternativa per superare i limiti del std::common_type quando applicato tipi integrali primitivi, è quello di definire un costume common_type.

Supponendo che ranged_integer sia definito come segue.

template<typename T, T min, T max> 
struct basic_ranged_integer; 

template<std::intmax_t min, std::intmax_t max> 
using ranged_integer = basic_ranged_integer<std::intmax_t, min, max>; 

Un'usanza common_type potrebbe essere definito come segue.

Prima la ricorsione sinistra:

template<typename... T> 
struct common_type; 

template<typename T, typename U, typename... V> 
struct common_type<T, U, V...> : 
     common_type<typename common_type<T, U>::type, V...> //left recursion 
{}; 

Ora le specializzazioni che coinvolgono basic_ranged_integer.

//two basic_ranged_integer 
template<typename T, T minT, T maxT, typename U, U minU, U maxU> 
struct common_type<basic_ranged_integer<T, minT, maxT>, basic_ranged_integer<U, minU, maxU>> 
{ 
    //gory details go here 
}; 

//basic_ranged_integer mixed with primitive integer types 
//forwards to the case involving two basic_ranged_integer 
template<typename T, T minT, T maxT, typename U> 
struct common_type<basic_ranged_integer<T, minT, maxT>, U> : 
     common_type 
     < 
      basic_ranged_integer<T, minT, maxT>, 
      typename make_ranged_integer<U>::type 
     > 
{}; 

template<typename T, typename U, U minU, U maxU> 
struct common_type<T, basic_ranged_integer<U, minU, maxU>> : 
     common_type 
     < 
      typename make_ranged_integer<T>::type, 
      basic_ranged_integer<U, minU, maxU> 
     > 
{}; 

E infine le specializzazioni che comportano una combinazione di numeri interi primari firmati e non firmati.

//base case: forwards to the satandard library 
template<typename T> 
struct common_type<T> : 
     std::common_type<T> 
{}; 

template<typename T, typename U> 
struct common_type<T, U> 
{ 
    static constexpr bool signed_xor = std::is_signed<T>{} xor std::is_signed<U>{}; 

    //base case: forwards to the satandard library 
    template<bool b = signed_xor, typename = void> 
    struct helper : 
      std::common_type<T, U> 
    {}; 

    //mixed signed/unsigned: forwards to the case involving two basic_ranged_integer 
    template<typename _ > 
    struct helper<true, _> : 
      common_type<typename make_ranged_integer<T>::type, typename make_ranged_integer<U>::type> 
    {}; 

    using type = typename helper<>::type; 
}; 

Nella suddetta make_ranged_integer dovrebbe prendere un tipo intero primitivo e definire type essere la desiderata corrispondente basic_ranged_integer.

+0

Sfortunatamente, 'OVERWRITE_DEFAULT_INTEGRAL_PROMOTION_RULES (int, long long);' ecc. Non è ancora sufficiente in quanto potrebbero esserci tipi interi estesi definiti dall'implementazione che non possono essere trattati in questo modo. Se si sta definendo il proprio 'common_type' (come suggerito), si potrebbe fare affidamento sull'operatore'?: 'E regolare la firma del risultato (tramite' is_signed' e 'make_signed'). – dyp

+0

@DyP hai ragione, quindi il mio avvertimento all'inizio della mia risposta, ma dal momento che l'OP non ha menzionato si aspetta che il meccanismo sia valido per nient'altro che i tipi primitivi e il suo 'ranged_integer' e, allo stesso tempo, voluto 'std :: common_type :: max() + 1>' per funzionare, non c'è altro modo, dobbiamo garantire 'std :: common_type 'non produce' unsigned'. Ancora una volta dovrei sottolineare che una metafunzione * comune di tipo comune * sarebbe la migliore. – brunocodutra

+0

Oh, mi è mancato, hai già detto "il cui caso base inoltra all'implementazione standard" per il custom_type personalizzato suggerito (questo risolverebbe questo problema). Bene, allora i tipi interi estesi aggiungono solo un secondo avvertimento per specializzare 'std :: common_type' qui. – dyp