Jumlah variabel argumen dalam C ++?

293

Bagaimana saya bisa menulis fungsi yang menerima sejumlah variabel argumen? Apakah ini mungkin?

nunos
sumber
49
Pada saat ini dengan C ++ 11 jawaban untuk pertanyaan ini akan sangat berbeda
K-ballo
1
@ K-ballo Saya menambahkan C ++ 11 contoh juga karena pertanyaan baru-baru ini menanyakan hal yang sama baru-baru ini dan saya merasa ini membutuhkan satu untuk membenarkan menutupnya stackoverflow.com/questions/16337459/…
Shafik Yaghmour
1
Menambahkan pra C ++ 11 opsi untuk jawaban saya juga, jadi sekarang harus mencakup sebagian besar pilihan yang tersedia.
Shafik Yaghmour
@ K-ballo ada afaik tidak ada cara untuk melakukannya di C ++ jika Anda membutuhkan tipe argumen paksa .. tidak ada konstruksi seperti foo (nilai int ...): / Jika Anda tidak peduli tentang jenis, maka ya, template variadic di C ++ 11 bekerja dengan baik
graywolf

Jawaban:

152

Anda mungkin tidak boleh, dan Anda mungkin bisa melakukan apa yang ingin Anda lakukan dengan cara yang lebih aman dan sederhana. Secara teknis untuk menggunakan jumlah variabel argumen di C Anda memasukkan stdarg.h. Dari itu Anda akan mendapatkan va_listjenis serta tiga fungsi yang beroperasi di dalamnya disebut va_start(), va_arg()dan va_end().

#include<stdarg.h>

int maxof(int n_args, ...)
{
    va_list ap;
    va_start(ap, n_args);
    int max = va_arg(ap, int);
    for(int i = 2; i <= n_args; i++) {
        int a = va_arg(ap, int);
        if(a > max) max = a;
    }
    va_end(ap);
    return max;
}

Jika Anda bertanya kepada saya, ini berantakan. Terlihat buruk, tidak aman, dan penuh dengan detail teknis yang tidak ada hubungannya dengan apa yang Anda coba capai secara konseptual. Sebagai gantinya, pertimbangkan untuk menggunakan overloading atau inheritance / polymorphism, pola builder (seperti dalam operator<<()stream) atau argumen default, dll. Ini semua lebih aman: kompiler mengetahui lebih banyak tentang apa yang Anda coba lakukan sehingga ada lebih banyak kesempatan dapat berhenti Anda sebelum Anda meledakkan kaki Anda.

wilhelmtell
sumber
7
Agaknya, Anda tidak bisa meneruskan referensi ke fungsi varargs karena kompiler tidak akan tahu kapan harus melewati nilai dan kapan dengan referensi, dan karena makro C yang mendasarinya tidak perlu tahu apa yang harus dilakukan dengan referensi - sudah ada batasan pada apa Anda dapat beralih ke fungsi C dengan argumen variabel karena hal-hal seperti aturan promosi.
Jonathan Leffler
3
apakah perlu memberikan setidaknya satu argumen sebelum ...sintaks?
Lazer
3
@ Jas bukan persyaratan bahasa atau pustaka, tetapi pustaka standar tidak memberi Anda cara untuk mengetahui panjang daftar. Anda membutuhkan penelepon untuk memberi Anda informasi ini atau entah bagaimana cara mengetahuinya sendiri. Dalam kasus printf(), misalnya, fungsi mem-parsing argumen string untuk token khusus untuk mencari tahu berapa banyak argumen tambahan yang harus diharapkan dalam daftar argumen variabel.
wilhelmtell
11
Anda mungkin harus menggunakan <cstdarg>dalam C ++ bukannya<stdarg.h>
newacct
11
Jumlah variabel argumen sangat bagus untuk debug atau untuk fungsi / metode yang mengisi beberapa array. Juga bagus untuk banyak operasi matematika, seperti maks, min, jumlah, rata-rata ... Ini tidak berantakan ketika Anda tidak mengacaukannya.
Tomáš Zato - Reinstate Monica
395

Dalam C ++ 11 Anda memiliki dua opsi baru, seperti halaman referensi fungsi Variadic di bagian Alternatif menyatakan:

  • Templat variadik juga dapat digunakan untuk membuat fungsi yang mengambil jumlah variabel argumen. Mereka sering kali merupakan pilihan yang lebih baik karena mereka tidak memaksakan pembatasan pada jenis argumen, tidak melakukan promosi integral dan floating-point, dan tipe yang aman. (sejak C ++ 11)
  • Jika semua argumen variabel memiliki tipe yang sama, std :: initializer_list menyediakan mekanisme yang nyaman (walaupun dengan sintaks yang berbeda) untuk mengakses argumen variabel.

Di bawah ini adalah contoh yang menunjukkan kedua alternatif ( lihat langsung ):

#include <iostream>
#include <string>
#include <initializer_list>

template <typename T>
void func(T t) 
{
    std::cout << t << std::endl ;
}

template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
    std::cout << t <<std::endl ;

    func(args...) ;
}

template <class T>
void func2( std::initializer_list<T> list )
{
    for( auto elem : list )
    {
        std::cout << elem << std::endl ;
    }
}

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    func(1,2.5,'a',str1);

    func2( {10, 20, 30, 40 }) ;
    func2( {str1, str2 } ) ;
} 

Jika Anda menggunakan gccatau clangkita dapat menggunakan variabel ajaib PRETTY_FUNCTION untuk menampilkan tipe tanda tangan dari fungsi yang dapat membantu dalam memahami apa yang sedang terjadi. Misalnya menggunakan:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

akan menghasilkan int berikut untuk fungsi variadik dalam contoh ( lihat langsung ):

void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello

Di Visual Studio Anda dapat menggunakan FUNCSIG .

Perbarui Pra C ++ 11

Pra C ++ 11 alternatif untuk std :: initializer_list akan menjadi std :: vector atau salah satu wadah standar lainnya :

#include <iostream>
#include <string>
#include <vector>

template <class T>
void func1( std::vector<T> vec )
{
    for( typename std::vector<T>::iterator iter = vec.begin();  iter != vec.end(); ++iter )
    {
        std::cout << *iter << std::endl ;
    }
}

int main()
{
    int arr1[] = {10, 20, 30, 40} ;
    std::string arr2[] = { "hello", "world" } ; 
    std::vector<int> v1( arr1, arr1+4 ) ;
    std::vector<std::string> v2( arr2, arr2+2 ) ;

    func1( v1 ) ;
    func1( v2 ) ;
}

dan alternatif untuk templat variadic akan menjadi fungsi variadic meskipun mereka bukan tipe-aman dan secara umum rawan kesalahan dan bisa tidak aman untuk digunakan tetapi satu-satunya alternatif potensial lainnya adalah dengan menggunakan argumen default , meskipun itu memiliki penggunaan terbatas. Contoh di bawah ini adalah versi modifikasi dari kode sampel dalam referensi yang ditautkan:

#include <iostream>
#include <string>
#include <cstdarg>

void simple_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);

    while (*fmt != '\0') {
        if (*fmt == 'd') {
            int i = va_arg(args, int);
            std::cout << i << '\n';
        } else if (*fmt == 's') {
            char * s = va_arg(args, char*);
            std::cout << s << '\n';
        }
        ++fmt;
    }

    va_end(args);
}


int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    simple_printf("dddd", 10, 20, 30, 40 );
    simple_printf("ss", str1.c_str(), str2.c_str() ); 

    return 0 ;
} 

Menggunakan fungsi variadic juga dilengkapi dengan batasan dalam argumen yang dapat Anda berikan yang dirinci dalam konsep C ++ standar di bagian 5.2.2 Function call paragraf 7 :

Ketika tidak ada parameter untuk argumen yang diberikan, argumen dilewatkan sedemikian rupa sehingga fungsi penerima dapat memperoleh nilai argumen dengan memanggil va_arg (18.7). Konversi standar lvalue-to-rvalue (4.1), array-to-pointer (4.2), dan function-to-pointer (4.3) dilakukan pada ekspresi argumen. Setelah konversi ini, jika argumen tidak memiliki aritmatika, enumerasi, penunjuk, penunjuk ke anggota, atau tipe kelas, program ini salah bentuk. Jika argumen memiliki tipe kelas non-POD (klausa 9), perilaku tidak terdefinisi. [...]

Shafik Yaghmour
sumber
Apakah penggunaan typenamevs Anda di classatas disengaja? Jika demikian, tolong jelaskan.
kevinarpe
1
@ kevinarpe tidak disengaja, itu tidak mengubah apa pun.
Shafik Yaghmour
Tautan pertama Anda mungkin seharusnya ke en.cppreference.com/w/cpp/language/variadic_arguments sebagai gantinya.
Alexey Romanov
apakah mungkin untuk membuat fungsi mengambil initializer_listrekursif?
idclev 463035818
33

Solusi C ++ 17: keamanan tipe penuh + sintaksis panggilan yang bagus

Karena pengenalan templat variadic di C ++ 11 dan lipat ekspresi dalam C ++ 17, dimungkinkan untuk mendefinisikan fungsi templat yang, di situs pemanggil, dapat dipanggil seolah-olah itu adalah fungsi varidic tetapi dengan keuntungan untuk :

  • tipe sangat aman;
  • bekerja tanpa informasi run-time dari jumlah argumen, atau tanpa penggunaan argumen "stop".

Berikut adalah contoh untuk tipe argumen campuran

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

Dan satu lagi dengan pencocokan jenis yang dipaksakan untuk semua argumen:

#include <type_traits> // enable_if, conjuction

template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;

template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
    std::cout << head;
    (std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!");   // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
                                               // print_same_type(3, ": ", "Hello, ", "World!");
                                                                                              ^

Informasi lebih lanjut:

  1. Templat variadik, juga dikenal sebagai paket parameter Paket parameter (sejak C ++ 11) - cppreference.com .
  2. Lipat ekspresi lipat ekspresi (sejak C ++ 17) - cppreference.com .
  3. Lihat demonstrasi program lengkap tentang coliru.
YSC
sumber
13
suatu hari nanti aku harap aku bisa membacatemplate<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian
1
@ Eladian membacanya sebagai "Hal ini diaktifkan hanya jika Headdan Tail... sama ", di mana " sama " berarti std::conjunction<std::is_same<Head, Tail>...>. Baca definisi terakhir ini sebagai " Headsama dengan semua Tail...".
YSC
24

di c ++ 11 yang dapat Anda lakukan:

void foo(const std::list<std::string> & myArguments) {
   //do whatever you want, with all the convenience of lists
}

foo({"arg1","arg2"});

daftar inisialisasi FTW!

Markus Zancolò
sumber
19

Dalam C ++ 11 ada cara untuk melakukan templat argumen variabel yang mengarah ke cara yang benar-benar elegan dan aman untuk memiliki fungsi argumen variabel. Bjarne sendiri memberikan contoh printf yang bagus menggunakan templat argumen variabel dalam C ++ 11FAQ .

Secara pribadi, saya menganggap ini sangat elegan sehingga saya bahkan tidak akan repot dengan fungsi argumen variabel dalam C ++ sampai kompiler itu memiliki dukungan untuk templat argumen variabel C ++ 11.

Beraneka ragam
sumber
@donlan - Jika Anda menggunakan C ++ 17, Anda dapat menggunakan ekspresi lipatan untuk membuat banyak hal lebih sederhana dalam beberapa kasus (pikirkan kreatif di sini, Anda dapat menggunakan ,operator dengan ekspresi lipatan). Kalau tidak, saya rasa tidak.
Mahakuasa
15

Fungsi variadik C-style didukung dalam C ++.

Namun, sebagian besar pustaka C ++ menggunakan idiom alternatif misalnya sedangkan 'c' printffungsi mengambil argumen variabel c++ coutobjek menggunakan <<overloading yang membahas keamanan tipe dan ADT (mungkin dengan biaya kesederhanaan implementasi).

Akan
sumber
Juga, ini hanya berfungsi jika ada fungsi seperti pencetakan, di mana Anda sebenarnya memiliki iterasi fungsi argumen tunggal di setiap argumen. Kalau tidak, Anda hanya menginisialisasi daftar dan melewati daftar untuk akhir sekitar std::initializer_lists... Dan ini sudah memperkenalkan kompleksitas besar pada tugas sederhana.
Christopher
13

Terlepas dari varargs atau overloading, Anda dapat mempertimbangkan untuk menjumlahkan argumen Anda dalam std :: vector atau wadah lain (std :: map misalnya). Sesuatu seperti ini:

template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);

Dengan cara ini Anda akan mendapatkan keamanan jenis dan makna logis dari argumen variadik ini akan terlihat jelas.

Tentunya pendekatan ini dapat memiliki masalah kinerja tetapi Anda tidak perlu khawatir tentang hal itu kecuali Anda yakin bahwa Anda tidak dapat membayar harganya. Ini adalah semacam pendekatan "Pythonic" untuk c ++ ...

Francesco
sumber
6
Cleaner adalah untuk tidak menegakkan vektor. Alih-alih menggunakan argumen templat yang menspesifikasikan koleksi STL-style kemudian beralih melalui itu menggunakan metode awal dan akhir argumen. Dengan cara ini Anda dapat menggunakan std :: vector <T>, c ++ 11's std :: array <T, N>, std :: initializer_list <T> atau bahkan membuat koleksi Anda sendiri.
Jens Åkerblom
3
@ JensÅkerblom Saya setuju tetapi ini adalah jenis pilihan yang harus dianalisis untuk masalah yang dihadapi, untuk menghindari over engineering. Karena ini adalah masalah tanda tangan API, penting untuk memahami pertukaran antara fleksibilitas maksimum dan kejelasan niat / kegunaan / pemeliharaan dll.
Francesco
8

Satu-satunya cara adalah melalui penggunaan argumen variabel gaya C, seperti dijelaskan di sini . Perhatikan bahwa ini bukan praktik yang disarankan, karena ini bukan typesafe dan rawan kesalahan.

Dave Van den Eynde
sumber
Karena rawan kesalahan, saya menganggap maksud Anda berpotensi sangat berbahaya? Terutama ketika bekerja dengan input yang tidak dipercaya.
Kevin Loney
Ya, tetapi karena masalah keamanan jenis. Pikirkan semua masalah yang mungkin terjadi pada printf biasa: format string yang tidak cocok dengan argumen yang diteruskan, dan sebagainya. printf menggunakan teknik yang sama, BTW.
Dave Van den Eynde
7

Tidak ada cara C ++ standar untuk melakukan ini tanpa menggunakan varargs C-style ( ...).

Tentu saja ada argumen default yang semacam "terlihat" seperti jumlah variabel argumen tergantung pada konteksnya:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

Keempat panggilan fungsi panggilan myfuncdengan berbagai jumlah argumen. Jika tidak ada yang diberikan, argumen default digunakan. Namun perlu dicatat, bahwa Anda hanya bisa menghilangkan argumen trailing. Tidak ada cara, misalnya untuk menghilangkan idan memberi saja j.

Zoli
sumber
4

Mungkin Anda ingin parameter kelebihan muatan atau default - tentukan fungsi yang sama dengan parameter bawaan:

void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
   // stuff
}

void doStuff( double std_termstator )
{
   // assume the user always wants '1' for the a param
   return doStuff( 1, std_termstator );
}

Ini akan memungkinkan Anda untuk memanggil metode dengan satu dari empat panggilan berbeda:

doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );

... atau Anda bisa mencari konvensi panggilan v_args dari C.

Kieveli
sumber
2

Jika Anda tahu kisaran jumlah argumen yang akan disediakan, Anda selalu dapat menggunakan beberapa fungsi yang berlebihan, seperti

f(int a)
    {int res=a; return res;}
f(int a, int b)
    {int res=a+b; return res;}

dan seterusnya...

Dunya Degirmenci
sumber
2

Menggunakan templat variadic, contoh untuk mereproduksi console.logseperti yang terlihat di JavaScript:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

Nama file misalnya js_console.h:

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};
lama12345
sumber
1

Seperti orang lain katakan, varargs C-style. Tetapi Anda juga dapat melakukan sesuatu yang mirip dengan argumen default.

Thomas Padron-McCarthy
sumber
Bisakah Anda menguraikan?
Dana Gugatan Monica
@QPaysTaxes: Untuk contoh bagaimana melakukannya dengan argumen default, lihat jawaban Zoltan.
Thomas Padron-McCarthy
0

Mungkin sekarang ... menggunakan sembarang dorongan dan templat. Dalam kasus ini, tipe argumen dapat dicampur

#include <boost/any.hpp>
#include <iostream>

#include <vector>
using boost::any_cast;

template <typename T, typename... Types> 
void Alert(T var1,Types... var2) 
{ 

    std::vector<boost::any> a(  {var1,var2...});

    for (int i = 0; i < a.size();i++)
    {

    if (a[i].type() == typeid(int))
    {
        std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(double))
    {
        std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
    }
    if (a[i].type() == typeid(const char*))
    {
        std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
    }
    // etc
    }

} 


void main()
{
    Alert("something",0,0,0.3);
}
Setelah terkejut
sumber
0

Gabungkan solusi C dan C ++ untuk opsi semantik yang paling sederhana, performan, dan paling dinamis. Jika Anda gagal, coba sesuatu yang lain.

// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
  T * arr = new T[n];
  va_list ap;
  va_start(ap, n);
  for (size_t i = 0; i < n; i++)
    T[i] = va_arg(ap,T);
  return arr;
}

Pengguna menulis:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

Secara semantik, ini terlihat dan terasa persis seperti fungsi n-argumen. Di bawah tenda, Anda dapat membukanya satu arah atau yang lain.

Christopher
sumber
-1

Kita juga bisa menggunakan initializer_list jika semua argumen adalah konst dan dari jenis yang sama

pkumar0
sumber
-2
int fun(int n_args, ...) {
   int *p = &n_args; 
   int s = sizeof(int);
   p += s + s - 1;
   for(int i = 0; i < n_args; i++) {
     printf("A1 %d!\n", *p);
     p += 2;
   }
}

Versi sederhana

Вениамин Раскин
sumber
1
Dan perilaku tidak terdefinisi yang tidak akan bekerja pada apa pun selain x86.
SS Anne