2013-08-02 5 views
34

Frage ist einfach, wie würde ich eine Funktion implementieren, die eine variable Anzahl von Argumenten (ähnlich der variadic Vorlage), aber wo alle Argumente den gleichen Typ haben, sagen Int.C++ 11 variable Anzahl von Argumenten, gleicher spezifischer Typ

Ich dachte über etwas Ähnliches nach;

Oder wird eine rekursive statische Assert auf den Typen arbeiten?

+3

Wenn Sie eine variable Anzahl von int-Argumenten benötigen, warum übergeben Sie keinen Vektor? Was werden diese Argumente tun? – Shark

+0

Oh>. <, Das könnte ich wirklich machen. Und ich spiele nur mit variadischen Vorlagen herum. – Skeen

+1

Verwenden Sie nicht "...". Die Ellipse ist eine gefährliche Verschleppung von C. Verwenden Sie stattdessen höhere C++ - Konstrukte und -Bibliotheken. ("C++ Coding Standards", Sutter/Alexandrescu) –

Antwort

38

Eine mögliche Lösung besteht darin, den Parametertyp zu einem Container zu machen, der von einer geschweiften Initialisierungsliste initialisiert werden kann, z. B. std::initializer_list<int> oder std::vector<int>. For example:

#include <iostream> 
#include <initializer_list> 

void func(std::initializer_list<int> a_args) 
{ 
    for (auto i: a_args) std::cout << i << '\n'; 
} 

int main() 
{ 
    func({4, 7}); 
    func({4, 7, 12, 14}); 
} 
+0

Angenommen, die Zahlen sind keine Konstanten. Funktioniert dieser Ansatz noch? – Skeen

+0

@Skeen, ja. Ich habe [hier] (http://ideone.com/YUQDQ1) überprüft, um sicher zu sein. – hmjd

+0

Das ist großartig, aber die Anzahl der Argumente hier ist nur zur Laufzeit bekannt. initializer_list fehlt eine constexpr-Version. Ich würde das brauchen :( –

9

Ich glaube, Sie können dies tun, konkreter durch die Angabe, wenn Ihre Argumente aus dem Argument Packung zu kauen. Etwas wie:

class MyClass{}; 
class MyOtherClass{}; 

void func() 
{ 
    // do something 
} 

template< typename... Arguments > 
void func(MyClass arg, Arguments ... args) 
{ 
    // do something with arg 
    func(args...); 
    // do something more with arg 
} 


void main() 
{ 
    MyClass a, b, c; 
    MyOtherClass d; 
    int i; 
    float f; 

    func(a, b, c); // compiles fine 
    func(i, f, d); // cannot convert 
} 

Im generischen Fall void func(MyClass arg, Arguments ... args) würde void func(arg, Arguments ... args) mit einem Template-Typ T. werden

2

Wenn Sie keine Klammer-basierte verwenden möchten initializer_list/vector und wollen, dass die Argumente getrennt halten in Form Argument Pack, dann unten Lösung prüft es bei der Kompilierung mit rekursiven static_assert s:

#include<type_traits> 

template<typename T1, typename T2, typename... Error> 
struct is_same : std::false_type {}; 

template<typename T, typename... Checking> 
struct is_same<T, T, Checking...> : is_same<T, Checking...> {}; 

template<typename T> 
struct is_same<T,T> : std::true_type {}; 

template<typename... LeftMost> 
void func (LeftMost&&... args) 
{ 
    static_assert(is_same<typename std::decay<LeftMost>::type...>::value, 
       "All types are not same as 'LeftMost'"); 
    // ... 
} 

int main() 
{ 
    int var = 2; 
    func(1,var,3,4,5); // ok 
    func(1,2,3,4.0,5); // error due to `static_assert` failure 
} 

Eigentlich wäre diese Lösung alle Argumente Witz überprüfen h Bezug auf das erste Argument. Angenommen, es wäre double, dann würde alles gegen double überprüft werden.

+2

Die Verwendung einer Forwarding-Referenz ('LeftMost &&') bedeutet, dass die statische Assertion fehlschlägt, wenn Sie die Funktion mit einer Mischung aus lvalues ​​und rvalues ​​aufrufen, zB 'int i = 0; func (i, 1); 'so wäre es besser zu überprüfen' ist_same :: type ...> 'stattdessen. –

+0

@ JonathanWakely, stimme zu. Vielen Dank! – iammilind

16

Hier ist eine Version, die die Funktion aus der Überladung entfernt, anstatt eine statische_Ausgabe zu geben. Auf diese Weise können Sie andere Überladungen der Funktion bereitstellen, die verwendet werden könnten, wenn die Typen nicht alle gleich sind, anstatt eine fatale static_assertion, die nicht vermieden werden kann.

#include <type_traits> 

template<typename... T> 
    struct all_same : std::false_type { }; 

template<> 
    struct all_same<> : std::true_type { }; 

template<typename T> 
    struct all_same<T> : std::true_type { }; 

template<typename T, typename... Ts> 
    struct all_same<T, T, Ts...> : all_same<T, Ts...> { }; 

template<typename... T> 
typename std::enable_if<all_same<T...>::value, void>::type 
func(T...) 
{ } 

Wenn Sie perfekte Weiterleitung unterstützen möchten Sie wahrscheinlich wollen die Typen zerfallen, bevor sie zu überprüfen, so dass die Funktion eine Mischung aus L-Wert und rvalue Argumente akzeptieren, solange sie die gleiche Art haben:

template<typename... T> 
typename std::enable_if<all_same<typename std::decay<T>::type...>::value, void>::type 
func(T&&...) 
{ } 

Alternativ, wenn Sie einen Allzweck-Merkmal für die logische Verbindung testen Sie es tun können std::is_same statt das Schreiben eigener all_same mit:

template<typename T, typename... Ts> 
typename std::enable_if<and_<is_same<T, Ts>...>::value, void>::type 
func(T&&, Ts&&...) 
{ } 

Da diese mindestens ein Argument erfordert würden Sie auch eine weitere Überlastung unterstützen müssen die Null Argument Fall: kann

void func() { } 

Der and_ Helfer wie so definiert werden:

template<typename...> 
    struct and_; 

template<> 
    struct and_<> 
    : public std::true_type 
    { }; 

template<typename B1> 
    struct and_<B1> 
    : public B1 
    { }; 

template<typename B1, typename B2> 
    struct and_<B1, B2> 
    : public std::conditional<B1::value, B2, B1>::type 
    { }; 

template<typename B1, typename B2, typename B3, typename... Bn> 
    struct and_<B1, B2, B3, Bn...> 
    : public std::conditional<B1::value, and_<B2, B3, Bn...>, B1>::type 
    { }; 
+1

Dies ist leider nicht 100% identisch mit dem putativen 'func (Foo ... foos)', denn wenn 'Foo' einen nicht-expliziten Konstruktor hat,' 'func ({x, y, z})' nicht Arbeit - der Compiler kann den Typ von '{x, y, z}' nicht ableiten, es sei denn, dieser Typ ist in der Signatur von 'func' angegeben. –

0

Weil ich don Ich glaube, ich sah diese Lösung, Sie könnten eine spezifische Funktion für jeden Typ schreiben (in Ihrem Fall nur int), dann eine Weiterleitungsfunktion, die variadische Argumenttypen verwendet.

Schreiben Sie jeden speziellen Fall:

dann für jeden spezifischen Fall:

// only int in your case 
void func(int i){ 
    std::cout << "int i = " << i << std::endl; 
} 

Dann Ihre Forwarding-Funktion wie folgt aus:

template<typename Arg0, typename Arg1 typename ... Args> 
void func(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){ 
    func(std::forward<Arg0>(arg0)); 
    func(std::forward<Arg1>(arg1), std::forward<Args>(args)...); 
} 

Das ist gut, weil es für, wenn Sie erweiterbar ist möchte vielleicht auch einen anderen Typ annehmen.

wie folgt verwendet:

int main(){ 
    func(1, 2, 3, 4); // works fine 
    func(1.0f, 2.0f, 3.0f, 4.0f); // compile error, no func(float) 
} 
4

@Skeen Wie wäre das?

template <typename T> 
void func_1(std::initializer_list<T>&& a) { 
    // do something 
} 

template <typename... T> 
void func(T&&... a) { 
    func_1({std::forward<T>(a)...}); 
} 

int main() { 
    func(1, 2, 3); 
    // func(1, 2, 3, 4.0); // OK doesn't compile 
} 
+0

@ John Carpenter OK. bearbeitet. – dusketha

+0

@Barry hast du es versucht? Ich tat bei http://www.tutorialspoint.com/codingground.htm sowohl literal int und int & sind bei Funktionsaufruf verfallen. – dusketha

Verwandte Themen