it-swarm.com.de

Geben Sie einen Typ für alle Argumente an, die ohne Verwendung von Array, Vektor, Strukturen usw. an variadic function oder variadic template function übergeben werden.

Ich erstelle eine Funktion (möglicherweise eine Elementfunktion, nicht, dass es darauf ankommt, oder doch?), Die eine unbekannte Anzahl von Argumenten akzeptieren muss, aber ich möchte, dass alle vom selben Typ sind. Ich weiß, dass ich ein Array oder einen Vektor übergeben kann, aber ich möchte die Liste der Argumente direkt akzeptieren können, ohne zusätzliche Struktur oder sogar zusätzliche Klammern. Es sieht nicht so aus, als wären verschiedene Funktionen für sich genommen typsicher, und ich war mir nicht sicher, wie ich mit diesen verschiedenen Vorlagenfunktionen umgehen sollte. Hier ist im Wesentlichen, was ich anstrebe (mehr als wahrscheinlich nicht korrekter Code und überhaupt nicht zum Zweck, Listen von Drachen zu bekommen, lol):

//typedef for dragon_list_t up here somewhere.

enum Maiden {
    Eunice
    , Beatrice
    , Una_Brow
    , Helga
    , Aida
};

dragon_list_t make_dragon_list(Maiden...) {
    //here be dragons
}

OR

template<Maiden... Maidens> dragon_list_t make_dragon_list(Maidens...) {
    //here be dragons
}

VERWENDUNGSZWECK

dragon_list_t dragons_to_slay
    = make_dragon_list(Maiden.Eunice, Maiden.Helga, Maiden.Aida)
;

Versuchte schon ein paar Dinge ähnlich wie oben, keine Würfel. Vorschläge? Offensichtliche Versehen, die ich vielleicht gemacht habe? Ich weiß, dass es keine große Sache sein kann, dies stattdessen zu tun:

dragon_list_t make_dragon_list(std::array<Maiden> maidens) {
    //here be dragons.
}
dragon_list_t dragons_to_slay
    = make_dragon_list({Maiden.Eunice, Maiden.Helga, Maiden.Aida})
;

aber ich wäre viel lieber in der Lage, es auf die erste Art und Weise zu tun, wenn es möglich ist.

34
Brett Rossier

Sie können die Argumente einfach mit der variadischen Vorlage akzeptieren und später bei der Konvertierung von Typechecking die Gültigkeit prüfen lassen.

Sie können die Konvertierbarkeit jedoch auf der Ebene der Funktionsschnittstelle überprüfen, um die Überladungsauflösung zu nutzen, um völlig falsche Argumente zurückzuweisen, z. B. mithilfe von SFINAE

template<typename R, typename...> struct fst { typedef R type; };

template<typename ...Args>
typename fst<void, 
  typename enable_if<
    is_convertible<Args, ToType>::value
  >::type...
>::type 
f(Args...);

Wenn Sie für Ihren Anwendungsfall die Schritte kennen, die erforderlich sind, um von einem std::array<> zu einem dragon_list_t zu gelangen, haben Sie dies bereits gemäß der obigen ersten Option gelöst ("convert-later"):

template<typename ...Items>
dragon_list_t make_dragon_list(Items... maidens) {
    std::array<Maiden, sizeof...(Items)> arr = {{ maidens ... }};
    // here be dragons
}

Wenn Sie dies mit dem obigen is_convertible-Ansatz kombinieren, verfügen Sie über eine Vorlage für frühzeitige Zurückweisung, die auch eine Überladungsauflösung für Argumente durchführt und diese gegebenenfalls zurückweist.

Da Sie das C++ 0x-Tag eingefügt haben, ist es naheliegend, nach Initialisierungslisten zu suchen. In einer Initialisierungsliste können Sie eine Reihe von Argumenten für einen ctor angeben, die zur Verarbeitung durch den ctor automatisch in eine einzelne Datenstruktur konvertiert werden.

Ihre primäre (ausschließliche?) Verwendung ist für genau die Art von Situation, die Sie erwähnt haben, und die Übergabe einer Reihe von Argumenten desselben Typs zur Erstellung einer Liste/eines Arrays/einer anderen Sammlung von Objekten. Es wird von (für ein Beispiel) std::vector unterstützt, daher können Sie Folgendes verwenden:

std::vector<dragon> dragons_to_slay{Eunice, Helga, Aida};

um einen Vektor aus drei dragon Objekten zu erstellen. Die meisten (alle?) Anderen Sammlungen werden die gleichen enthalten. Wenn Sie also wirklich auf einer Liste von Drachen bestehen, sollten Sie in der Lage sein, diese auch ziemlich leicht zu bekommen.

11
Jerry Coffin

Wenn Sie für den Parameter, der nicht im Paket enthalten ist, nicht template verwenden, wird die Funktion variadic so aufgelöst, dass alle Argumente vom gleichen Typ sind.

Hier ist ein Beispiel für eine erweiterte Funktion max, die nur ints akzeptiert (oder Typen, die in int konvertierbar sind).

int maximum(int n) // last argument must be an `int`
{
    return n;
}

template<typename... Args>
int maximum(int n, Args... args) // first argument must be an int
{
    return std::max(n, maximum(args...));
}

Erläuterung: Wenn Sie das Argumentpaket (args...) entpacken, sucht der Compiler nach der besten Überladung. Wenn das Paket nur einen Parameter hatte, ist der einzige Kandidat maximum(int), daher muss der einzige Parameter vom Typ int sein (oder in int konvertierbar sein). Wenn das Paket mehr als ein Element enthält, ist maximum(int, typename...) der einzige Kandidat. Daher muss das erste Argument vom Typ int sein (oder in int konvertierbar). Es ist einfach, durch Induktion zu beweisen, dass alle Typen in der Packung von einem Typ sein müssen, der in int konvertierbar ist.

10
Motti

Ich musste kürzlich ein Parameterpaket auf nur einen Typ beschränken oder zumindest in diesen Typ konvertieren. Am Ende habe ich einen anderen Weg gefunden:

#include <type_traits>
#include <string>

template <template<typename> class Trait, typename Head, typename ...Tail> 
struct check_all {
  enum { value = Trait<Head>::value && check_all<Trait, Tail...>::value };
};

template <template<typename> class Trait, typename Head>
struct check_all<Trait, Head> {
  enum { value = Trait<Head>::value };
};

template <typename ...Args> 
struct foo {
  // Using C++11 template alias as compile time std::bind
  template <typename T>
  using Requirement = std::is_convertible<double, T>;
  static_assert(check_all<Requirement, Args...>::value, "Must convert to double");
};

int main() {
  foo<int, char, float, double>();
  foo<int, std::string>(); // Errors, no conversion
}

Das, was mir an dieser Lösung gefallen hat, ist, dass ich check_all auch auf andere Eigenschaften anwenden kann.

4
Flexo

Obwohl die Frage mit C++ 11 gekennzeichnet ist, sollte meiner Meinung nach eine Lösung mit C++ 17 + -Konzepten hinzugefügt werden, da es jetzt Unterstützung für GCC gibt, und andere werden bald folgen.

definieren Sie zunächst ein einfaches Konzept

class mytype{};

template<typename T>
concept bool MyType = std::is_same<T, mytype>::value;

verwenden Sie dann einfach verschiedene Vorlagenparameter

template<MyType ... Args>
void func(Args &&... args){
    // do something here
}

Viel einfacher mit dem Aufkommen von Konzepten!

2
CoffeeandCode

Ein neuer Vorschlag, Homogene Variadic Functions , behandelt dies, indem er so etwas wie Ihr erstes Konstrukt legal macht. Außer natürlich, um das Parameterpaket zu verwenden, müssen Sie es benennen. Auch die genaue Syntax scheint noch nicht sehr konkret zu sein.

Nach dem Vorschlag ist dies also tatsächlich legal (ein ähnliches Konstrukt finden Sie im Abschnitt "Der Vorlageneinführer" im Artikel):

dragon_list_t make_dragon_list(Maiden... maidens) {
    //here be dragons
}
2
Jan Hošek

Es kommt wirklich darauf an, was Sie implementieren möchten.

Normalerweise kennzeichnet enum Laufzeituntertypen einer bestimmten Klasse oder eine diskriminierte Vereinigung (boost :: variant). In diesem Fall möchten Sie jedoch die enum direkt übergeben. Darüber hinaus haben Sie eine begrenzte Anzahl möglicher Werte, und jeder Funktionsaufruf bildet eine Teilmenge. Wirklich, was Sie darstellen, ist eine Teilmenge, überhaupt nicht mehrere Parameter.

Die beste Art, eine Teilmenge einer endlichen Menge darzustellen, ist eine Bitmenge. Große Mengen sollten std::bitset verwenden; Kleine Sets können einfach unsigned long verwenden.

enum Maiden_set {
    Eunice = 1,
    , Beatrice = 2
    , Una_Brow = 4
    , Helga = 8
    , Aida = 16
};

dragon_list_t make_dragon_list(Maiden_set) {
    //here be dragons
}

make_dragon_list( Eunice + Beatrice + Helga );

oder, da Sie zur Kompilierungszeit scheinbar mit Variationen umgehen möchten,

template< int Maidens > // parameter is not a Maiden_set because enum+enum=int
dragon_list_t make_dragon_list() {
    //here be dragons
}

make_dragon_list< Eunice + Beatrice + Helga >(); // + promotes each enum to int

Es sollte möglich sein, die Potenzen von 2 automatisch mit einem operator+ zu generieren, der auf den Typ enum überladen ist. Aber ich bin mir nicht sicher, ob ich überhaupt auf dem richtigen Weg bin.

1
Potatoswatter

Kurz gesagt, Sie sollten wahrscheinlich nur einen Vektor erstellen. Es ist nicht so viel Aufwand, besonders wenn Sie so etwas wie boost :: list_of oder die Initialisierungsliste von C++ 0x verwenden. Der syntaktische Aufwand ist minimal und flexibler (Sie könnten eine Liste mit einer Reihe von Argumenten übergeben, die nur zur Laufzeit bekannt sind).

Wenn Sie es wirklich wollen, können Sie verschiedene Vorlagenparameter verwenden, um dies zu tun:

// Using pass-by-value since I'm assuming it is primitive:

template< typename T, typename... Args>
void make_dragon_list_internal( dragon_list_t* dragon_list, T t, Args... args )
{
   // add T to dragon_list.
   make_dragon_list_internal( dragon_list, args... );
}

void make_dragon_list_internal( dragon_list_t* dragon_list )
{
   // Finalize dragon_list.
}

template<typename... Args>
dragon_list_t make_dragon_list( Args... args )
{
  dragon_list_t dragon_list;
  make_dragon_list_internal( &dragon_list, args... );
  return dragon_list;
}

Es ist typsicher und erweiterbar (wenn Sie möchten, können Sie dafür auch andere Dinge als Drachen verwenden).

1
Todd Gardner

Ich würde versuchen, die Dinge einfach zu halten, und die einfachste Lösung, die ich mir vorstellen kann, ist die Verwendung eines einfachen alten Vektors. Durch die Verwendung von C++ 0x-Funktionen können Sie eine Syntax erhalten, die der gewünschten Syntax ähnlich ist (auch wenn diese nicht exakt ist):

void foo( std::vector<int> const & v ) {
   std::copy( v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ") );
}
int main() {
  foo({ 1, 2, 3, 4, 5, 6 }); // note the extra {}
}

Ich denke, der folgende Code ist hilfreich für Ihren Fall:

template <class...>
struct IsAllSame {};

template <class T, class B1>
struct IsAllSame<T, B1> {
  static constexpr const bool kValue = std::is_same<T, B1>::value;
};

template <class T, class B1, class... Bn>
struct IsAllSame<T, B1, Bn...> {
  static constexpr const bool kValue =
      IsAllSame<T, B1>::kValue ? IsAllSame<T, Bn...>::kValue : false;
};

IsAllSame<int>::kValue == true
IsAllSame<bool, int>::kValue == false
IsAllSame<bool, int, int>::kValue == false
0
Nan Hua