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

[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++“

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

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

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3