C++ invoke와 apply | "함수 호출" 유틸리티 가이드

C++ invoke와 apply | "함수 호출" 유틸리티 가이드

이 글의 핵심

C++ invoke와 apply에 대해 정리한 개발 블로그 글입니다. #include <functional> using namespace std;

std::invoke

#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

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: 멤버 함수 래퍼

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: 튜플 기반 함수 호출

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

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);  // 정수
}

멤버 포인터

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: 멤버 함수 호출

// ❌ 직접 호출 불가
auto func = &Widget::getValue;
// func();  // 에러

// ✅ invoke 사용
Widget w;
invoke(func, w);

문제 2: apply 인자 순서

// ❌ 순서 중요
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: 참조 캡처

// ❌ 복사
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 직접 호출

// 직접 호출
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++“

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ tuple apply | “튜플 적용” 가이드
  • C++ CTAD | “클래스 템플릿 인자 추론” 가이드
  • C++ bind | “함수 바인딩” 가이드

관련 글

  • C++ tuple apply |
  • C++ any |
  • C++ bind |
  • 모던 C++ (C++11~C++20) 핵심 문법 치트시트 | 현업에서 자주 쓰는 한눈에 보기
  • C++ CTAD |