Bagaimana saya bisa menulis fungsi yang menerima sejumlah variabel argumen? Apakah ini mungkin?
Bagaimana saya bisa menulis fungsi yang menerima sejumlah variabel argumen? Apakah ini mungkin?
Jawaban:
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_list
jenis 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.
...
sintaks?
printf()
, misalnya, fungsi mem-parsing argumen string untuk token khusus untuk mencari tahu berapa banyak argumen tambahan yang harus diharapkan dalam daftar argumen variabel.
<cstdarg>
dalam C ++ bukannya<stdarg.h>
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 gcc
atau clang
kita 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. [...]
typename
vs Anda di class
atas disengaja? Jika demikian, tolong jelaskan.
initializer_list
rekursif?
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 :
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:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
dan Tail...
sama ", di mana " sama " berarti std::conjunction<std::is_same<Head, Tail>...>
. Baca definisi terakhir ini sebagai " Head
sama dengan semua Tail...
".
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!
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.
,
operator dengan ekspresi lipatan). Kalau tidak, saya rasa tidak.
Fungsi variadik C-style didukung dalam C ++.
Namun, sebagian besar pustaka C ++ menggunakan idiom alternatif misalnya sedangkan 'c' printf
fungsi mengambil argumen variabel c++ cout
objek menggunakan <<
overloading yang membahas keamanan tipe dan ADT (mungkin dengan biaya kesederhanaan implementasi).
std::initializer_lists
... Dan ini sudah memperkenalkan kompleksitas besar pada tugas sederhana.
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 ++ ...
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.
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 myfunc
dengan 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 i
dan memberi saja j
.
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.
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...
Menggunakan templat variadic, contoh untuk mereproduksi console.log
seperti 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;
}
};
Seperti orang lain katakan, varargs C-style. Tetapi Anda juga dapat melakukan sesuatu yang mirip dengan argumen default.
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);
}
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.
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