C ++ 템플릿 함수를 인수로 전달하는 규칙을 찾고 있습니다.
이것은 C ++에서 지원하는 예제입니다.
#include <iostream>
void add1(int &v)
{
v+=1;
}
void add2(int &v)
{
v+=2;
}
template <void (*T)(int &)>
void doOperation()
{
int temp=0;
T(temp);
std::cout << "Result is " << temp << std::endl;
}
int main()
{
doOperation<add1>();
doOperation<add2>();
}
그러나이 기술에 대해 배우기는 어렵습니다. “템플릿 인수로서의 기능”에 대한 인터넷 검색은 그다지 중요하지 않습니다. 그리고 고전적인 C ++ Templates The Complete Guide도 놀랍게도 토론하지 않습니다 (적어도 내 검색은 아닙니다).
내가 가진 질문은 이것이 유효한 C ++ (또는 널리 지원되는 확장)인지 여부입니다.
또한 이러한 종류의 템플릿 호출 중에 동일한 서명을 가진 functor를 명시 적 함수와 호환 가능하게 사용할 수있는 방법이 있습니까?
구문이 틀리기 때문에 다음 프로그램은 적어도 Visual C ++ 에서 작동 하지 않습니다 . 사용자 정의 비교 연산을 정의하려는 경우 함수 포인터 또는 functor를 std :: sort 알고리즘에 전달할 수있는 방법과 유사하게 functor의 함수를 전환 할 수 있고 그 반대도 가능합니다.
struct add3 {
void operator() (int &v) {v+=3;}
};
...
doOperation<add3>();
웹 링크 또는 두 개 또는 C ++ 템플릿 책의 페이지에 대한 포인터는 높이 평가됩니다!
답변
예, 유효합니다.
functors에서도 작동하게 만드는 일반적인 해결책은 다음과 같습니다.
template <typename F>
void doOperation(F f)
{
int temp=0;
f(temp);
std::cout << "Result is " << temp << std::endl;
}
이제 다음 중 하나로 호출 할 수 있습니다.
doOperation(add2);
doOperation(add3());
이것의 문제점은 컴파일러가 호출을 인라인하는 것이 까다로워지면 add2
함수 포인터 유형 void (*)(int &)
이 전달되고 있다는 것 doOperation
입니다. (그러나 add3
functor 인 인라인은 쉽게 인라인 될 수 있습니다. 여기서 컴파일러는 유형의 객체가 add3
함수에 전달 된다는 것을 알고 있습니다. 이는 호출 할 함수가 add3::operator()
알 수없는 함수 포인터가 아니라 임을 의미합니다 .)
답변
템플릿 매개 변수는 유형 (typename T) 또는 값 (int X)으로 매개 변수화 할 수 있습니다.
코드 조각을 템플릿 화하는 “전통적인”C ++ 방식은 펑터 (functor)를 사용하는 것입니다. 즉, 코드는 객체에 있으므로 객체는 코드에 고유 한 유형을 부여합니다.
전통적인 함수로 작업 할 때이 기술은 잘 작동하지 않습니다. 유형 변경은 특정 함수를 나타내지 않기 때문에 가능한 많은 함수의 서명 만 지정하기 때문입니다. 그래서:
template<typename OP>
int do_op(int a, int b, OP op)
{
return op(a,b);
}
int add(int a, int b) { return a + b; }
...
int c = do_op(4,5,add);
functor 케이스와 동일하지 않습니다. 이 예제에서 서명이 int X (int, int) 인 모든 함수 포인터에 대해 do_op가 인스턴스화됩니다. 이 경우를 완전히 인라인하려면 컴파일러가 매우 공격적이어야합니다. (컴파일러 최적화가 상당히 발전함에 따라 배제하지는 않습니다.)
이 코드가 우리가 원하는 것을 수행하지 못한다는 것을 알리는 한 가지 방법은 다음과 같습니다.
int (* func_ptr)(int, int) = add;
int c = do_op(4,5,func_ptr);
여전히 합법적이며 분명히 인라인되지 않습니다. 전체 인라인을 얻으려면 값으로 템플릿을 작성해야하므로 템플릿에서 기능을 완전히 사용할 수 있습니다.
typedef int(*binary_int_op)(int, int); // signature for all valid template params
template<binary_int_op op>
int do_op(int a, int b)
{
return op(a,b);
}
int add(int a, int b) { return a + b; }
...
int c = do_op<add>(4,5);
이 경우 인스턴스화 된 각 버전의 do_op은 이미 사용 가능한 특정 기능으로 인스턴스화됩니다. 따라서 우리는 do_op의 코드가 “return a + b”처럼 보일 것으로 기대합니다. (Lips programmers, 당신의 능글 맞은 웃음을 멈 추세 요!)
또한 이것이 다음과 같은 이유로 우리가 원하는 것에 더 가깝다는 것을 확인할 수 있습니다.
int (* func_ptr)(int,int) = add;
int c = do_op<func_ptr>(4,5);
컴파일에 실패합니다. GCC는 “오류 : ‘func_ptr’은 상수 표현으로 나타날 수 없습니다. 즉, 컴파일러 시간에 우리의 op가 무엇인지 알 수있는 정보를 충분히 얻지 못했기 때문에 do_op을 완전히 확장 할 수 없습니다.
두 번째 예가 실제로 우리의 op를 완전히 요약하고 있고 첫 번째 예가 그렇지 않다면 템플릿은 무엇입니까? 뭐하는거야? 답은 유형 강제입니다. 첫 번째 예 에서이 리프는 작동합니다.
template<typename OP>
int do_op(int a, int b, OP op) { return op(a,b); }
float fadd(float a, float b) { return a+b; }
...
int c = do_op(4,5,fadd);
그 예는 효과가 있습니다! (나는 그것이 좋은 C ++을 제안하지는 않지만 …) 무슨 일이 있었는지 do_op은 다양한 함수 의 시그니처 주위에 템플릿 화되어 있으며 각 개별 인스턴스화는 다른 유형의 강제 코드를 작성합니다. 따라서 fadd가있는 do_op의 인스턴스화 된 코드는 다음과 같습니다.
convert a and b from int to float.
call the function ptr op with float a and float b.
convert the result back to int and return it.
이와 비교해 볼 때, 값 단위의 경우 함수 인수와 정확히 일치해야합니다.
답변
함수 포인터는 템플릿 매개 변수로 전달 될 수 있으며 이는 표준 C ++의 일부입니다
. 그러나 템플릿에서 이들은 포인터 대 함수가 아닌 함수로 선언되고 사용됩니다. 템플릿 인스턴스화 에서 이름이 아닌 함수의 주소를 전달합니다.
예를 들면 다음과 같습니다.
int i;
void add1(int& i) { i += 1; }
template<void op(int&)>
void do_op_fn_ptr_tpl(int& i) { op(i); }
i = 0;
do_op_fn_ptr_tpl<&add1>(i);
functor 유형을 템플릿 인수로 전달하려면 다음을 수행하십시오.
struct add2_t {
void operator()(int& i) { i += 2; }
};
template<typename op>
void do_op_fntr_tpl(int& i) {
op o;
o(i);
}
i = 0;
do_op_fntr_tpl<add2_t>(i);
몇 가지 답변이 functor 인스턴스를 인수로 전달합니다.
template<typename op>
void do_op_fntr_arg(int& i, op o) { o(i); }
i = 0;
add2_t add2;
// This has the advantage of looking identical whether
// you pass a functor or a free function:
do_op_fntr_arg(i, add1);
do_op_fntr_arg(i, add2);
템플릿 인수를 사용하여이 균일 한 모양을 얻을 수있는 가장 가까운 do_op
것은 비 유형 매개 변수로 두 번 정의 하고 유형 매개 변수로 한 번 정의 하는 것입니다.
// non-type (function pointer) template parameter
template<void op(int&)>
void do_op(int& i) { op(i); }
// type (functor class) template parameter
template<typename op>
void do_op(int& i) {
op o;
o(i);
}
i = 0;
do_op<&add1>(i); // still need address-of operator in the function pointer case.
do_op<add2_t>(i);
솔직히, 나는 이것이 컴파일되지 않기를 정말로 기대했지만 gcc-4.8 및 Visual Studio 2013에서 나에게 효과적이었습니다.
답변
템플릿에서
template <void (*T)(int &)>
void doOperation()
매개 변수 T
는 유형이 아닌 템플리트 매개 변수입니다. 이는 템플릿 함수의 동작이 매개 변수 값 (함수 포인터 상수가 컴파일 타임에 수정되어야 함)에 따라 변경됨을 의미합니다.
함수 객체와 함수 매개 변수 모두에서 작동하는 무언가를 원한다면 형식화 된 템플릿이 필요합니다. 그러나이를 수행 할 때 런타임에 함수에 오브젝트 인스턴스 (함수 오브젝트 인스턴스 또는 함수 포인터)를 제공해야합니다.
template <class T>
void doOperation(T t)
{
int temp=0;
t(temp);
std::cout << "Result is " << temp << std::endl;
}
약간의 성능 고려 사항이 있습니다. 이 새로운 버전은 특정 함수 포인터가 런타임에만 참조되고 호출되는 반면 함수 포인터 템플릿은 사용되는 특정 함수 포인터에 따라 함수 호출 인라인으로 최적화 될 수 있으므로 함수 포인터 인수를 사용하면 효율성이 떨어질 수 있습니다. 함수 개체의 유형에 따라 특정 operator()
개체가 완전히 결정되기 때문에 함수 개체는 형식이 지정된 템플릿을 사용하여 매우 효율적으로 확장 할 수 있습니다 .
답변
functor 예제가 작동하지 않는 이유는를 호출하기 위해 인스턴스가 필요하기 때문 operator()
입니다.
답변
편집 : 연산자를 참조로 전달해도 작동하지 않습니다. 간단하게하기 위해 함수 포인터로 이해하십시오. 당신은 포인터가 아닌 참조를 보냅니다. 나는 당신이 이와 같은 것을 쓰려고 노력하고 있다고 생각합니다.
struct Square
{
double operator()(double number) { return number * number; }
};
template <class Function>
double integrate(Function f, double a, double b, unsigned int intervals)
{
double delta = (b - a) / intervals, sum = 0.0;
while(a < b)
{
sum += f(a) * delta;
a += delta;
}
return sum;
}
. .
std::cout << "interval : " << i << tab << tab << "intgeration = "
<< integrate(Square(), 0.0, 1.0, 10) << std::endl;