2015-04-17 6 views
22

Eine Klassenvorlage kann mehrere Parameter haben, die alle Standardwerte haben.Explizit Standardwerte für einige Parameter bei der Klassenvorlageninstanziierung verwenden

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

die Vorlage mit nur Standardparameter Instatiating ist einfach:

options<> my_default_options; 

Aber was, wenn ich eine Teilmenge der Parameter ändern möchten?

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

Es ist nicht offensichtlich, dass int ein Standard für den ersten Parameter ist, während für den zweiten ist es nicht. Gibt es so etwas wie

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

in C++?

+4

'template limited_options = Optionen verwendet;' könnte das tun, was Sie fordern? Ich glaube nicht, dass das Standardschlüsselwort in diesem Beispiel akzeptiert wird (obwohl es sinnvoll wäre). –

Antwort

19

Nein, im Standard C++ gibt es nichts, was dies ermöglichen würde. Eine Möglichkeit, bereits von @FlorisVelleman in den Kommentaren, ist eine Alias-Vorlage vorstellen:

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

Dies hat den Nachteil, dass sie explizit das Standardargument von UnderlyingT0 in der Alias-Definition kopieren, aber als am wenigsten ‚es dupliziert nur an einem Ort.

Eine alternative Option wird von vielen Boost-Bibliotheken verwendet. Sie führen ein spezielles Tag use_default ein und machen das den Standardwert. Etwas wie folgt aus:

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; 
}; 

Hier sind die Nachteile sind, dass 1. Sie nicht die Standardargumente, indem man die Template-Deklaration erklären kann, und 2. options<> und options<int, long, std::string> sind verschiedene Typen.

Ersteres kann durch gute Dokumentation behoben werden, und letzterem kann wahrscheinlich durch vernünftige Verwendung von Konvertierungsfunktionen und Basisklassen geholfen werden.

+0

Das ist gut zu wissen. Ich schreibe gerade eine Boost-Bibliothek, also sollte ich wahrscheinlich diesen Ansatz verwenden. – iFreilicht

+0

Auf der anderen Seite, dies funktioniert nicht mit nicht-type Vorlage Argumente, während Sebastian Redls Idee tut. – iFreilicht

14

Es gibt keine Möglichkeit, die Standardparameter direkt wiederzuverwenden. Sie können den Kommentar von Floris als Möglichkeit verwenden, Abkürzungen für häufige Verwendungen bereitzustellen, aber der Vorlagenalias wiederholt die Standardwerte.

Alternativ könnte die Optionen struct Parameter Umschalten aus, damit eingestellt werden:

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>; 
}; 

und dann als

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

Das ist ein cooler Trick, +1. – Angew

+0

Fluent Interface in der Kompilierung, gibt es ein erstes Mal für alles :) +1 – Barry

6

verwenden Wenn alle Ihre Template-Argumente Standardwerte wie in Ihrem Beispiel haben, Sie könnten eine Hilfsstruktur erstellen, um sie für Sie zu extrahieren.

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; 

Dann ist es wie folgt verwenden:

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

Another cooler Trick, +1. – Angew

+0

Sehr nette Trick, der Nachteil ist natürlich, dass es unordentlich wird, wenn Sie nur den letzten Parameter ändern möchten. Aber es ist ein sehr interessanter Begriff an und für sich. +1 – iFreilicht

1

ich auf diese Frage kam wieder und kam mit einer allgemeineren Version von Sebastian Redl-Lösung auf.

//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; 

Vorteile:

  • Flexible Auswahl von Parametern
  • ändern erfordert nicht der ursprünglichen Klasse zu ändern
  • Klassen unterstützt, die ohne Ausfälle einige Parameter haben
  • options<int,long,int> und with_n_t<2,int,options<>> sind vom gleichen Typ

Einige Anwendungsbeispiele:

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> 

Sie könnten weiter verallgemeinern diese variadische Paare von Indizes und Typen zu nehmen, damit Sie Nest brauchen nicht with_n_t.

+0

Coole Idee, aber wenn ich mich nicht irre, bietet Sebastian Redls Lösung drei Ihrer vier Vorteile bereits, außer dass Ihre ursprüngliche Klasse unverändert bleibt. Aber das bedeutet, dass Sie nur eine Kopfzeile mit Ihrem Code importieren und verwenden müssen, wo immer Sie wollten, was nett ist. Ich persönlich mag die Syntax 'Optionen <> :: WithT0 ' ein bisschen besser aber, weil die Klasse, die Sie tatsächlich verwenden sind zu Beginn der Anweisung geschrieben ist, nicht am Ende. Das scheint Code viel klarer zu machen. – iFreilicht

+1

Ja, ich dachte nur, es wäre ein weiterer interessanter Weg, um das Problem zu lösen. Sie könnten diese Syntax erhalten, indem Sie ein Mixin mit dem Namen 'DefaultsSetable' oder etwas erstellen und dann Ihre Klasse von dieser Klasse erben lassen, um die Funktionalität zu übernehmen. Auf diese Weise könnten Sie 'Optionen <> :: mitigtt <0,int> :: with_n_t <1,float>' schreiben. – TartanLlama

Verwandte Themen