Jumlah variabel argumen dalam C ++?


293

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


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.


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. [...]


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.

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!


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.


@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).


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 ++ ...


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.


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.


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.


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...


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


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

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.


-1

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


-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


1
Dan perilaku tidak terdefinisi yang tidak akan bekerja pada apa pun selain x86.
SS Anne
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.