2015-04-17 10 views
22

Un modello di classe può avere più parametri che hanno tutti i valori predefiniti.Utilizzare in modo esplicito i valori predefiniti per alcuni parametri nell'istanza del modello di classe

template<typename UnderlyingT0 = int, typename UnderlyingtT1 = long, typename StringT = std::string> 
struct options; 

Instatiating il modello con solo i parametri di default è facile:

options<> my_default_options; 

Ma cosa succede se voglio cambiare un sottoinsieme di parametri?

options<int, int, std::wstring> wstring_options; 

Non è ovvio che int è un predefinito per il primo parametro mentre per il secondo non lo è. C'è qualcosa come

options<default, int, std::wstring> wstring_options; 

in C++?

+4

'template using limited_options = options ;' potrebbe fare quello che stai chiedendo? Non penso che la parola chiave predefinita sia accettata in questo esempio (anche se avrebbe senso). –

risposta

19

No, non c'è nulla nel C++ standard che consentirebbe questo. Una possibilità, notato da @FlorisVelleman nei commenti, è quello di introdurre un modello di alias:

template <class UnderlyingT1, class StringT = std::string> 
using options_defT0 = options<int, UnderlyingT1, StringT>; 

Questo ha l'inconveniente di dover duplicare in modo esplicito l'argomento di default del UnderlyingT0 nella definizione di alias, ma come minimo e' duplicato solo in un posto.

Un'opzione alternativa viene utilizzata da molte librerie Boost. Introducono un tag speciale use_default e rendono quello il valore predefinito. Qualcosa di simile a questo:

struct use_default {}; 

template<typename UnderlyingT0 = use_default, typename UnderlyingtT1 = use_default, typename StringT = use_default> 
struct options 
{ 
    using RealUnderlyingT0 = typename std::conditional< 
    std::is_same<UnderlyingT0, use_default>::value, 
    int, 
    UnderlyingT0 
    >::type; 

    using RealUnderlyingT1 = typename std::conditional< 
    std::is_same<UnderlyingT1, use_default>::value, 
    long, 
    UnderlyingT1 
    >::type; 

    using RealStringT = typename std::conditional< 
    std::is_same<StringT, use_default>::value, 
    std::string, 
    StringT 
    >::type; 
}; 

Qui, i lati negativi sono che: 1. non si può dire gli argomenti predefiniti, cercando in dichiarazione modello e 2. options<> e options<int, long, std::string> sono diversi tipi.

Il primo può essere risolto con una buona documentazione e quest'ultimo può probabilmente essere aiutato dall'uso giudizioso delle funzioni di conversione e delle classi di base.

+0

Buono a sapersi. Sto scrivendo una libreria di boost in questo momento, quindi dovrei probabilmente usare questo approccio. – iFreilicht

+0

D'altra parte, questo non funziona con argomenti modello non di tipo, mentre l'idea di Sebastian Redls lo fa. – iFreilicht

14

Non esiste un modo per riutilizzare direttamente i parametri predefiniti. È possibile utilizzare il commento di Floris come metodo per fornire abbreviazioni per gli usi comuni, ma l'alias del modello continuerà a ripetere i valori predefiniti.

In alternativa, la struct opzioni potrebbe essere impostato in modo da consentire il passaggio fuori parametri:

template <typename UnderlyingT0 = int, 
      typename UnderlyingT1 = long, 
      typename StringT = std::string> 
struct options { 
    template <typename NewT0> 
    using WithT0 = options<NewT0, UnderylingT1, StringT>; 
    template <typename NewT1> 
    using WithT1 = options<UnderylingT0, NewT1, StringT>; 
    template <typename NewStringT> 
    using WithStringT = options<UnderylingT0, UnderylingT1, NewStringT>; 
}; 

e quindi utilizzarlo come

options<>::WithT1<int>::WithStringT<std::wstring> 
+1

Questo è un bel trucco, +1. – Angew

+0

Ottima interfaccia in fase di compilazione, c'è una prima volta per tutto :) +1 – Barry

6

Se tutti i vostri argomenti di template hanno valori di default, come nel tuo esempio, potresti creare una struttura di supporto per estrarli per te.

template <class T, size_t N> 
struct default_for_helper; 

template <template <typename...> class T, size_t N, typename... Args> 
struct default_for_helper<T<Args...>, N> 
{ 
    using type = std::tuple_element_t<N, std::tuple<Args...>>; 
}; 

template <template <typename...> class T, size_t N> 
using default_for = typename default_for_helper<T<>, N>::type; 

Quindi utilizzare in questo modo:

options<default_for<options,0>, int, std::string> o; 
+0

Un altro trucco interessante, +1. – Angew

+0

Trucco molto bello, ovviamente il lato negativo è che diventa complicato quando si desidera modificare solo l'ultimo parametro. Ma è un concetto molto interessante in sé e per sé. +1 – iFreilicht

1

mi sono imbattuto in questo problema di nuovo e si avvicinò con una versione più generale di soluzione di Sebastian Redl.

//given an index to replace at, a type to replace with and a tuple to replace in 
//return a tuple of the same type as given, with the type at ReplaceAt set to ReplaceWith 
template <size_t ReplaceAt, typename ReplaceWith, size_t... Idxs, typename... Args> 
auto replace_type (std::index_sequence<Idxs...>, std::tuple<Args...>) 
    -> std::tuple<std::conditional_t<ReplaceAt==Idxs, ReplaceWith, Args>...>; 

//instantiates a template with the types held in a tuple 
template <template <typename...> class T, typename Tuple> 
struct type_from_tuple; 

template <template <typename...> class T, typename... Ts> 
struct type_from_tuple<T, std::tuple<Ts...>> 
{ 
    using type = T<Ts...>; 
}; 

//replaces the type used in a template instantiation of In at index ReplateAt with the type ReplaceWith 
template <size_t ReplaceAt, typename ReplaceWith, class In> 
struct with_n; 

template <size_t At, typename With, template <typename...> class In, typename... InArgs> 
struct with_n<At, With, In<InArgs...>> 
{ 
    using tuple_type = decltype(replace_type<At,With> 
     (std::index_sequence_for<InArgs...>{}, std::tuple<InArgs...>{})); 

    using type = typename type_from_tuple<In,tuple_type>::type; 
}; 

//convenience alias 
template <size_t ReplaceAt, typename ReplaceWith, class In> 
using with_n_t = typename with_n<ReplaceAt, ReplaceWith, In>::type; 

Vantaggi:

  • selezione flessibile dei parametri di cambiare
  • Non è necessario cambiare la classe originale
  • Supporta le classi che hanno alcuni parametri senza default
  • options<int,long,int> e with_n_t<2,int,options<>> sono dello stesso tipo

Alcuni esempi di utilizzo:

with_n_t<1, int, options<>> a; //options<int, int, std::string> 
with_n_t<2, int, 
    with_n_t<1, int, options<>>> b; //options<int, int, int> 

Si potrebbe ulteriormente generalizzare questo per prendere coppie variadic di indici e tipi in modo che non c'è bisogno di nido with_n_t.

+0

Idea interessante, ma a meno che non mi sbagli, la soluzione di Sebastian Redls offre già tre dei tuoi quattro vantaggi, tranne il fatto che il tuo consente alla classe originale di rimanere invariata. Ma questo vuol dire che dovresti solo importare un'intestazione con il tuo codice e usarla dove vuoi, il che è bello. Personalmente preferisco la sintassi 'options <> :: WithT0 ' un po 'meglio, perché la classe che stai effettivamente usando è scritta all'inizio dell'istruzione, non alla fine. Ciò sembra rendere il codice molto più chiaro. – iFreilicht

+1

Sì, ho pensato che fosse un altro modo interessante per risolvere il problema. È possibile ottenere tale sintassi creando un mixin chiamato 'DefaultsSable' o qualcosa, quindi rendere la classe ereditata da quella classe per ereditare la funzionalità. In questo modo potresti scrivere 'options <> :: with_n_t <0,int> :: with_n_t <1,float>'. – TartanLlama

Problemi correlati