C ++에서 가변 개수의 인수?


293

가변 개수의 인수를 허용하는 함수를 작성하려면 어떻게해야합니까? 이것이 가능합니까?


49
현재 C ++ 11 에서이 질문에 대한 답은 크게 다를 것입니다.
K-ballo

1
@ K-ballo 최근 질문이 최근에 이와 같은 질문을했기 때문에 C ++ 11 예제도 추가했으며, 닫는 것을 정당화하기 위해 이것이 필요하다고 느꼈습니다. stackoverflow.com/questions/16337459/…
Shafik Yaghmour

1
내 대답에 C ++ 11 사전 옵션을 추가 했으므로 이제는 사용 가능한 대부분의 선택 사항을 다루어야합니다.
Shafik Yaghmour

@ K-ballo 강제 인수 유형이 필요한 경우 C ++에서 할 수있는 afaik 방법이 없습니다. C ++ 11에서 잘 작동합니다.
graywolf

답변:


152

아마해서는 안되며 아마도 더 안전하고 간단한 방법으로 원하는 것을 할 수 있습니다. 기술적으로 C에서 가변 개수의 인수를 사용하려면 stdarg.h를 포함하십시오. 당신이 얻을 것이다 것과 va_list유형뿐 아니라 호출에서 작동 세 가지 기능 va_start(), va_arg()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;
}

당신이 저에게 묻는다면, 이것은 혼란입니다. 나쁘게 보이고 안전하지 않으며 개념적으로 달성하려는 것과는 아무런 관련이없는 기술적 인 세부 사항으로 가득합니다. 대신, 오버로드 또는 상속 / 다형성, operator<<()스트림 에서 와 같은 빌더 패턴 또는 기본 인수 등을 사용하는 것이 좋습니다. 이들은 모두 더 안전합니다. 다리를 날리기 전에


7
아마도 컴파일러는 값으로 전달할 때와 참조 할 때를 알지 못하고 기본 C 매크로가 참조로 수행 할 작업을 반드시 알지 못하기 때문에 varargs 함수에 참조를 전달할 수 없습니다. 승격 규칙과 같은 이유로 변수 인수를 사용하여 C 함수에 전달할 수 있습니다.
Jonathan Leffler

3
...구문 전에 최소한 하나의 인수를 제공해야 합니까?
Lazer

3
@Lazer 언어 나 라이브러리 요구 사항은 아니지만 표준 라이브러리는 목록의 길이를 알려주는 수단을 제공하지 않습니다. 이 정보를 제공하거나 다른 방법으로 자신을 파악하려면 발신자가 필요합니다. printf()예를 들어 의 경우 함수는 특수 토큰에 대한 문자열 인수를 구문 분석하여 변수 인수 목록에 몇 개의 추가 인수가 있어야하는지 파악합니다.
wilhelmtell 2016 년

11
아마 <cstdarg>C ++ 대신에 C ++로 사용해야합니다<stdarg.h>
newacct

11
가변 개수의 인수는 디버그 또는 일부 배열을 채우는 함수 / 메소드에 적합합니다. 또한 max, min, sum, average와 같은 많은 수학적 연산에 좋습니다. 엉망이되지 않아도 혼란스럽지 않습니다.
Tomáš Zato-Reinstate Monica

395

에서 C ++ (11) 당신은 같은 두 가지 새로운 옵션이 가변 인자 함수 참조 페이지 대안 섹션 상태 :

  • 가변 템플릿을 사용하여 가변 개수의 인수를 취하는 함수를 만들 수도 있습니다. 인수 유형에 제한을 두지 않고 정수 및 부동 소수점 승격을 수행하지 않으며 유형이 안전하기 때문에 종종 더 나은 선택입니다. (C ++ 11부터)
  • 모든 변수 인수가 공통 유형을 공유하는 경우 std :: initializer_list는 변수 인수에 액세스하기위한 편리한 메커니즘을 제공합니다 (다른 구문에도 불구하고).

아래는 두 가지 대안을 모두 보여주는 예입니다 ( 실제 참조 ).

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

PRETTY_FUNCTION 매직 변수 를 사용 gcc하거나 clang사용중인 경우 진행 상황을 이해하는 데 도움이되는 함수의 유형 서명을 표시 할 수 있습니다. 예를 들어

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

예제의 가변 함수에 대해 int 다음과 같은 결과를 얻 습니다 (실제 참조 ).

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

Visual Studio에서는 FUNCSIG 를 사용할 수 있습니다 .

Pre C ++ 11 업데이트

C ++ (11) 에 대한 대안 표준 : initializer_list는표준 : : 벡터 또는 다른 하나의 표준 컨테이너 :

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

과에 대한 대안 가변 인자 템플릿이 될 것 가변 기능을 가되지 않더라도 안전 입력 및 일반적으로 발생하기 쉬운 오류 및 사용이 안전하지 않을 수 있지만, 단지 다른 잠재적 인 대안은 사용하는 것입니다 기본 인자를 그 사용이 제한을 가지고 있지만,. 아래 예제는 링크 된 참조에있는 샘플 코드의 수정 된 버전입니다.

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

variadic 함수를 사용하면 전달할 수있는 인수에 제한이 있습니다. 함수 호출 단락 7 섹션의 C ++ 표준 초안에 자세히 나와 있습니다 .5.2.2

주어진 인수에 대한 매개 변수가없는 경우, 수신 함수가 va_arg (18.7)를 호출하여 인수의 값을 얻을 수있는 방식으로 인수가 전달됩니다. lvalue-to-rvalue (4.1), array-to-pointer (4.2) 및 function-to-pointer (4.3) 표준 변환은 인수 표현식에서 수행됩니다. 이러한 변환 후 인수에 산술, 열거, 포인터, 멤버에 대한 포인터 또는 클래스 유형이없는 경우 프로그램이 잘못 구성됩니다. 인수에 비 POD 클래스 유형 (9 항)이있는 경우 동작이 정의되지 않습니다. [...]


귀하의 typenamevs class사용량이 의도적입니까? 그렇다면 설명하십시오.
kevinarpe

1
@kevinarpe는 의도적이지 않지만 아무것도 변경하지 않습니다.
Shafik Yaghmour

첫 번째 링크는 아마도 en.cppreference.com/w/cpp/language/variadic_arguments에 대한 것일 수 있습니다 .
Alexey Romanov

initializer_list재귀를 취하는 함수를 만들 수 있습니까?
idclev 463035818

33

C ++ 17 솔루션 : 풀 타입 안전 + 멋진 호출 구문

C ++ 11에서 가변 템플릿을 도입하고 C ++ 17에서 접기 식을 도입하므로 호출자 사이트에서 마치 가변 함수 인 것처럼 호출 할 수 있지만 다음과 같은 이점이있는 템플릿 함수를 정의 할 수 있습니다. :

  • 안전하고 강력해야합니다.
  • 인수 수에 대한 런타임 정보가 없거나 "중지"인수를 사용하지 않고 작동합니다.

혼합 인수 유형에 대한 예는 다음과 같습니다.

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

그리고 모든 인수에 대해 유형 일치가 강제 된 다른 항목 :

#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!");
                                                                                              ^

추가 정보:

  1. 또한 매개 변수 팩으로 알려진 가변 인자 템플릿, 매개 변수 팩 (11 ++ C부터) - cppreference.com .
  2. 폴드 표현식 접기 표현식 (C ++ 17부터)-cppreference.com .
  3. 콜리 루에 대한 전체 프로그램 데모 를 참조하십시오 .

13
언젠가 내가 읽을 수 있기를 바란다template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Eladian

1
로 읽기 "이 일 경우에만 사용할 수 있습니다 @Eladian HeadTail... 동일하다 ", 여기서 " 동일 수단" std::conjunction<std::is_same<Head, Tail>...>. "" Head는 모두 Tail..." 와 동일 합니다.
YSC

24

C ++ 11에서는 다음을 수행 할 수 있습니다.

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

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

리스트 이니셜 라이저 FTW!


19

C ++ 11에는 변수 인수 템플릿을 수행하는 방법이있어 변수 인수 함수를 갖는 정말 우아하고 유형이 안전한 방법을 제공합니다. Bjarne 자신은 C ++ 11FAQ변수 인수 템플릿사용하여 printf 의 좋은 예를 제공합니다 .

개인적으로, 나는이 컴파일러가 C ++ 11 변수 인수 템플릿을 지원할 때까지 C ++에서 변수 인수 함수를 신경 쓰지 않을 정도로 우아하다고 생각합니다.


@donlan-C ++ 17을 사용하는 경우 접는 식을 사용하여 경우에 따라 훨씬 간단하게 만들 수 있습니다 (창의적으로 생각하면 ,접는 식으로 연산자를 사용할 수 있음 ). 그렇지 않으면 그렇게 생각하지 않습니다.
전능 한

15

C 스타일의 가변 함수는 C ++에서 지원됩니다.

그러나 대부분의 C ++ 라이브러리는 대체 관용구를 사용합니다. 예를 들어 'c' printf함수는 변수 안전 인수를 c++ cout사용하여 <<유형 안전 및 ADT를 처리 하는 오버로드를 사용합니다 (아마도 구현 단순화 비용).


또한 이것은 인쇄와 같은 함수의 경우에만 작동하는 것 같습니다. 여기서 실제로 각 인수에 걸쳐 단일 인수 함수를 반복합니다. 그렇지 않으면, 당신은 목록을 초기화하고 끝을 위해 목록을 전달하는 것입니다 std::initializer_lists... 그리고 이것은 이미 간단한 작업에서 엄청난 복잡성을 소개하고 있습니다.
Christopher

13

varargs 또는 오버로드와는 별도로 std :: vector 또는 다른 컨테이너 (예 : std :: map)에서 인수를 집계하는 것을 고려할 수 있습니다. 이 같은:

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

이런 식으로 유형 안전성을 얻을 수 있으며 이러한 가변성 인수의 논리적 의미가 명백합니다.

확실히이 방법에는 성능 문제가있을 수 있지만 가격을 지불 할 수 있다고 확신하지 않으면 걱정할 필요가 없습니다. C ++에 대한 일종의 "Pythonic"접근 방식입니다.


6
클리너는 벡터를 적용하지 않는 것입니다. 대신 STL 스타일 컬렉션을 지정하는 템플릿 인수를 사용하고 인수의 begin 및 end 메소드를 사용하여이를 반복합니다. 이 방법으로 std :: vector <T>, c ++ 11의 std :: array <T, N>, std :: initializer_list <T>를 사용하거나 자신의 컬렉션을 만들 수도 있습니다.
Jens Åkerblom

3
@ JensÅkerblom 나는 동의하지만 이것은 과도한 엔지니어링을 피하기 위해 당면한 문제에 대해 분석 해야하는 종류의 선택입니다. 이는 API 서명의 문제이므로 최대한의 유연성과 의도 / 사용 가능성 / 유지 가능성 등의 명확성 간의 상충 관계를 이해하는 것이 중요합니다.
Francesco

8

여기에 설명 된대로 C 스타일 변수 인수를 사용하는 것이 유일한 방법입니다 . 형식이 안전하고 오류가 발생하지 않으므로 권장되지 않습니다.


오류가 발생하기 쉽기 때문에 잠재적으로 매우 위험하다고 생각하십니까? 특히 신뢰할 수없는 입력으로 작업 할 때.
케빈 로니

예, 그러나 유형 안전 문제 때문입니다. 일반 printf가 가질 수있는 모든 가능한 문제를 생각하십시오 : 전달 된 인수와 일치하지 않는 형식 문자열 등. printf는 동일한 기술인 BTW를 사용합니다.
Dave Van den Eynde 09:11

7

C 스타일 varargs ( ...) 를 사용하지 않고이를 수행하는 표준 C ++ 방법은 없습니다 .

물론 상황에 따라 가변 개수의 인수처럼 "보이는"기본 인수가 있습니다.

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

// other code...

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

네 가지 함수 호출은 myfunc모두 다양한 개수의 인수로 호출합니다. 아무 것도 지정하지 않으면 기본 인수가 사용됩니다. 그러나 후행 인수 만 생략 할 수 있습니다. 예를 들어 생략 i하고 제공 하는 방법은 없습니다 j.


4

오버로드 또는 기본 매개 변수를 원할 수 있습니다. 기본 매개 변수를 사용하여 동일한 기능을 정의하십시오.

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

이렇게하면 네 가지 다른 호출 중 하나를 사용하여 메소드를 호출 할 수 있습니다.

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

... 또는 C에서 v_args 호출 규칙을 찾고있을 수 있습니다.


2

제공 될 인수의 수의 범위를 알고 있다면 다음과 같은 함수 오버로드를 항상 사용할 수 있습니다.

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

등등...


2

다양한 템플릿을 사용하여 console.logJavaScript에서 볼 수있는 예 :

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

파일 이름 예 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

부스트 및 템플릿을 사용하여 가능합니다.이 경우 인수 유형을 혼합 할 수 있습니다

#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

의미 적으로 가장 간단하고 성능이 뛰어나며 가장 동적 인 옵션을 위해 C 및 C ++ 솔루션을 결합하십시오. 실수하면 다른 것을 시도하십시오.

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

사용자 글 :

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

의미 상 이것은 n- 인수 함수와 똑같이 보이고 느낍니다. 후드 아래에서 포장을 풀 수 있습니다.


-1

모든 인수가 const이고 동일한 유형 인 경우 initializer_list를 사용할 수도 있습니다.


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

일반 버전


1
그리고 x86 이외의 다른 것에서는 작동하지 않는 정의되지 않은 동작.
SS Anne
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.