[2026] C++ invoke와 apply | 함수 호출 유틸리티 가이드
이 글의 핵심
C++ invoke와 apply의 핵심 개념과 실무 포인트를 정리합니다.
std::invoke
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <functional>
using namespace std;
int add(int a, int b) {
return a + b;
}
struct Calculator {
int multiply(int a, int b) {
return a * b;
}
int value = 10;
};
int main() {
// 일반 함수
cout << invoke(add, 2, 3) << endl; // 5
// 람다
auto lambda = { return x * 2; };
cout << invoke(lambda, 5) << endl; // 10
// 멤버 함수
Calculator calc;
cout << invoke(&Calculator::multiply, calc, 3, 4) << endl; // 12
// 멤버 변수
cout << invoke(&Calculator::value, calc) << endl; // 10
}
std::apply
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
int add(int a, int b, int c) {
return a + b + c;
}
int main() {
tuple<int, int, int> args = {1, 2, 3};
// 튜플 언팩
int result = apply(add, args);
cout << result << endl; // 6
}
실전 예시
예시 1: 제네릭 콜백
template<typename Func, typename....Args>
auto callWithLogging(Func&& func, Args&&....args) {
cout << "함수 호출 시작" << endl;
auto result = invoke(forward<Func>(func), forward<Args>(args)...);
cout << "함수 호출 완료" << endl;
return result;
}
int main() {
auto result = callWithLogging(add, 2, 3);
cout << "결과: " << result << endl;
}
예시 2: 멤버 함수 래퍼
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename T, typename Func, typename....Args>
auto callMember(T& obj, Func func, Args&&....args) {
return invoke(func, obj, forward<Args>(args)...);
}
class Widget {
public:
void setName(const string& name) {
this->name = name;
cout << "이름 설정: " << name << endl;
}
string getName() const {
return name;
}
private:
string name;
};
int main() {
Widget w;
callMember(w, &Widget::setName, "MyWidget");
string name = callMember(w, &Widget::getName);
cout << name << endl;
}
예시 3: 튜플 기반 함수 호출
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename Func, typename Tuple>
auto callWithTuple(Func&& func, Tuple&& args) {
return apply(forward<Func>(func), forward<Tuple>(args));
}
int multiply(int a, int b, int c) {
return a * b * c;
}
int main() {
auto args = make_tuple(2, 3, 4);
int result = callWithTuple(multiply, args);
cout << result << endl; // 24
}
예시 4: 지연 실행
template<typename Func, typename....Args>
class DeferredCall {
private:
Func func;
tuple<Args...> args;
public:
DeferredCall(Func f, Args....a) : func(f), args(a...) {}
auto execute() {
return apply(func, args);
}
};
template<typename Func, typename....Args>
auto defer(Func func, Args....args) {
return DeferredCall(func, args...);
}
int main() {
auto deferred = defer(add, 2, 3);
cout << "나중에 실행..." << endl;
int result = deferred.execute();
cout << "결과: " << result << endl;
}
invoke_result
다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename Func, typename....Args>
void printReturnType(Func func, Args....args) {
using ReturnType = invoke_result_t<Func, Args...>;
if constexpr (is_same_v<ReturnType, void>) {
cout << "반환 타입: void" << endl;
} else if constexpr (is_integral_v<ReturnType>) {
cout << "반환 타입: 정수" << endl;
} else {
cout << "반환 타입: 기타" << endl;
}
}
int main() {
printReturnType(add, 1, 2); // 정수
}
멤버 포인터
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct Widget {
int value;
void setValue(int v) {
value = v;
}
int getValue() const {
return value;
}
};
int main() {
Widget w;
// 멤버 함수 포인터
auto setFunc = &Widget::setValue;
invoke(setFunc, w, 42);
// 멤버 변수 포인터
auto valuePtr = &Widget::value;
cout << invoke(valuePtr, w) << endl; // 42
}
자주 발생하는 문제
문제 1: 멤버 함수 호출
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 직접 호출 불가
auto func = &Widget::getValue;
// func(); // 에러
// ✅ invoke 사용
Widget w;
invoke(func, w);
문제 2: apply 인자 순서
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 순서 중요
int subtract(int a, int b) {
return a - b;
}
auto args = make_tuple(3, 10);
cout << apply(subtract, args) << endl; // -7 (3 - 10)
// ✅ 순서 확인
auto args2 = make_tuple(10, 3);
cout << apply(subtract, args2) << endl; // 7 (10 - 3)
문제 3: 참조 캡처
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 복사
int x = 10;
auto args = make_tuple(x);
x = 20;
apply( { cout << y << endl; }, args); // 10
// ✅ 참조
auto args2 = make_tuple(ref(x));
x = 20;
apply( { cout << y << endl; }, args2); // 20
invoke vs 직접 호출
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 직접 호출
add(2, 3);
calc.multiply(3, 4);
// invoke (제네릭)
invoke(add, 2, 3);
invoke(&Calculator::multiply, calc, 3, 4);
invoke 장점:
- 통일된 인터페이스
- 멤버 함수/변수 지원
- 완벽 전달
FAQ
Q1: invoke는 언제 사용하나요?
A:
- 제네릭 콜백
- 멤버 함수 포인터
- 통일된 함수 호출
Q2: apply는 언제 사용하나요?
A:
- 튜플 언팩
- 가변 인자 전달
- 지연 실행
Q3: 성능 오버헤드는?
A: 인라인화로 오버헤드 없음.
Q4: invoke vs 직접 호출?
A: 제네릭 코드에서 invoke 사용.
Q5: apply vs 가변 인자 템플릿?
A:
- apply: 튜플에서 언팩
- 가변 인자: 직접 전달
Q6: invoke/apply 학습 리소스는?
A:
- cppreference.com
- “C++17: The Complete Guide”
- “Effective Modern C++“
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.