본문으로 건너뛰기
Previous
Next
C++ tuple apply | 'Application of tuples' guide

C++ tuple apply | 'Application of tuples' guide

C++ tuple apply | 'Application of tuples' guide

이 글의 핵심

C++ tuple apply - "Tuple application" guide. What is apply in C++ tuple apply?, basic usage, and practical examples are explained along with practical code.

What is apply?

std::apply is a function introduced in C++17 that unpacks the elements of a tuple into function arguments. This is useful when passing values ​​stored in a tuple to a function.

#include <tuple>

// 변수 선언 및 초기화
int add(int a, int b, int c) {
    return a + b + c;
}

std::tuple<int, int, int> args{1, 2, 3};

// apply: unpack tuple
int result = std::apply(add, args);  // add(1, 2, 3)

Why do you need it?:

  • Tuple Unpack: Convert tuples to function arguments
  • Lazy call: Save arguments in advance and call later
  • Metaprogramming: Variable argument handling
  • Simplicity: No need for index-based access

The following example demonstrates the concept in cpp:

// ❌ Index-based: hassle
std::tuple<int, int, int> args{1, 2, 3};
int result = add(std::get<0>(args), std::get<1>(args), std::get<2>(args));

// ✅ apply: concise
int result = std::apply(add, args);

How ​​apply works:

Here is the apply_impl implementation:

// Conceptual Implementation
template<typename Func, typename Tuple, size_t....Indices>
auto apply_impl(Func&& func, Tuple&& tuple, std::index_sequence<Indices...>) {
    return func(std::get<Indices>(std::forward<Tuple>(tuple))...);
}

template<typename Func, typename Tuple>
auto apply(Func&& func, Tuple&& tuple) {
    return apply_impl(
        std::forward<Func>(func),
        std::forward<Tuple>(tuple),
        std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{}
    );
}

apply vs direct call:

Featuresdirect callstd::apply
save argument❌ Not possible✅ Available
delayed call❌ Not possible✅ Available
variable arguments❌ Difficulty✅ Easy
Performance✅ Fast✅ Inline Capable

The following example demonstrates the concept in cpp:

// direct call
int result1 = add(1, 2, 3);

// apply: Called after storing arguments
auto args = std::make_tuple(1, 2, 3);
int result2 = std::apply(add, args);

Default use

#include <tuple>

void print(int x, double y, const std::string& z) {
    std::cout << x << ", " << y << ", " << z << std::endl;
}

int main() {
    auto args = std::make_tuple(42, 3.14, "hello");
    
    std::apply(print, args);  // print(42, 3.14, "hello")
}

Practical example

Example 1: Lambda

#include <tuple>

int main() {
    auto args = std::make_tuple(10, 20, 30);
    
    auto result = std::apply([](int a, int b, int c) {
        return a + b + c;
    }, args);
    
std::cout << "sum: " << result << std::endl;  // 60
}

Example 2: Constructor

#include <tuple>
#include <memory>

struct Widget {
    int x;
    double y;
    std::string z;
    
    Widget(int x, double y, std::string z) 
        : x(x), y(y), z(std::move(z)) {}
};

int main() {
    auto args = std::make_tuple(42, 3.14, std::string{"hello"});
    
    // After passing constructor arguments to apply, make_unique
    auto widget = std::apply([](int x, double y, std::string z) {
        return std::make_unique<Widget>(x, y, std::move(z));
    }, args);
    
    std::cout << widget->x << ", " << widget->y << ", " << widget->z << std::endl;
}

Example 3: Function wrapper

Here is the execute implementation:

#include <tuple>
#include <functional>

template<typename Func, typename....Args>
class DelayedCall {
    Func func;
    std::tuple<Args...> args;
    
public:
    DelayedCall(Func f, Args....a) 
        : func(f), args(std::forward<Args>(a)...) {}
    
    auto execute() {
        return std::apply(func, args);
    }
};

int main() {
    auto delayed = DelayedCall{
        [](int a, int b) { return a + b; },
        10, 20
    };
    
std::cout << "Result: " << delayed.execute() << std::endl;  // 30
}

Example 4: Variable arguments

#include <tuple>

template<typename....Args>
void logArgs(Args&&....args) {
    auto tuple = std::make_tuple(std::forward<Args>(args)...);
    
    std::apply([](const auto&....values) {
        ((std::cout << values << " "), ...);
        std::cout << std::endl;
    }, tuple);
}

int main() {
    logArgs(1, 2.5, "hello", true);
    // 1 2.5 hello 1
}

make_from_tuple

#include <tuple>

struct Point {
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
};

int main() {
    auto args = std::make_tuple(10, 20);
    
    // make_from_tuple: Constructor call
    auto point = std::make_from_tuple<Point>(args);
    
    std::cout << point.x << ", " << point.y << std::endl;
}

Frequently occurring problems

Issue 1: References

The following example demonstrates the concept in cpp:

int x = 42;
auto args = std::make_tuple(x);  // copy

// ✅ See also
auto args2 = std::forward_as_tuple(x);  // reference

std::apply([](int& val) {
    val = 100;
}, args2);

std::cout << x << std::endl;  // 100

Problem 2: Number of arguments

void func(int a, int b) {
    std::cout << a + b << std::endl;
}

// ❌ Number of arguments mismatch
// auto args = std::make_tuple(1, 2, 3);
// std::apply(func, args);  // error

// ✅ Accurate count
auto args = std::make_tuple(1, 2);
std::apply(func, args);

Problem 3: Type inference

// auto: complex type
auto t = std::make_tuple(42, 3.14);

// explicit type
std::tuple<int, double> t2{42, 3.14};

Issue 4: Performance

The following example demonstrates the concept in cpp:

// apply can be inline
// Minimize overhead

// But the cost of creating a tuple is
auto args = std::make_tuple(1, 2, 3);  // copy
std::apply(func, args);

// ✅ Direct calls (if available)
func(1, 2, 3);

Utilization pattern

Here is the process implementation:

// 1. Return multiple values
std::tuple<int, std::string> parse();

// 2. Save function arguments
auto args = std::make_tuple(1, 2, 3);
std::apply(func, args);

// 3. Constructor call
auto obj = std::make_from_tuple<T>(args);

// 4. Variable argument handling
template<typename....Args>
void process(Args&&....args);

Practice pattern

Pattern 1: Asynchronous operations

Here is the asyncApply implementation:

#include <tuple>
#include <future>

template<typename Func, typename....Args>
auto asyncApply(Func&& func, std::tuple<Args...> args) {
    return std::async(std::launch::async, [func = std::forward<Func>(func), args = std::move(args)]() {
        return std::apply(func, args);
    });
}

// use
int compute(int a, int b, int c) {
    return a * b + c;
}

auto args = std::make_tuple(10, 20, 5);
auto future = asyncApply(compute, args);
std::cout << "Result: " << future.get() << '\n';  // 205

Pattern 2: Function Cache

When using a set of arguments as keys, you can uniformly call a variable argument function with std::tuple + std::apply.

#include <map>
#include <tuple>
#include <functional>

template<typename Func>
class MemoizedBinary {
    Func func_;
    std::map<std::pair<int, int>, int> cache_;

public:
    explicit MemoizedBinary(Func f) : func_(std::move(f)) {}

    int operator()(int a, int b) {
        auto key = std::make_pair(a, b);
        if (auto it = cache_.find(key); it != cache_.end()) {
            return it->second;
        }
        auto tup = std::make_tuple(a, b);
        int result = std::apply(func_, tup);
        cache_.emplace(key, result);
        return result;
    }
};

// use
auto add_cached = MemoizedBinary([](int a, int b) { return a + b; });

Pattern 3: Batch processing

Here is the add implementation:

template<typename Func>
class BatchProcessor {
    Func func_;
    std::vector<std::tuple<int, int>> batch_;
    
public:
    BatchProcessor(Func func) : func_(func) {}
    
    void add(int a, int b) {
        batch_.emplace_back(a, b);
    }
    
    void process() {
        for (auto& args : batch_) {
            auto result = std::apply(func_, args);
std::cout << "Result: " << result << '\n';
        }
        batch_.clear();
    }
};

// use
BatchProcessor processor([](int a, int b) {
    return a + b;
});

processor.add(1, 2);
processor.add(3, 4);
processor.process();
// Result: 3
// Result: 7

Advanced use of std::apply

  • Combination with std::invoke: When calling a function contained in a member pointer or optional, std::invoke comes to mind first, and if the argument set is a tuple, it is resolved with std::apply. The first argument of apply is a callable object, so the lambda, function object, and binding results are entered as is.
  • Return type: Return type is inferred using decltype(auto) or std::invoke_result_t, and is verified at compile time in the template API whether “if a tuple is inserted, it matches the function signature”.
  • const tuple: If you pass a read-only tuple like std::apply(f, std::as_const(t)), it becomes clear whether the element is modifiable when it is a reference.

Unpack function arguments: tuple vs parameter pack

methodWhen to useMemo
Parameter Pack (...)Passing template variable arguments directlyPerfect forward idiom with std::forward
tuple + applyWhen a bundle is determined at runtime, or save/move as a single valueThe number of arguments must be fixed to compile
make_from_tupleWhen matching the contents of the tuple as is to the constructorThe explicit constructor can also be called

If you already have (args...) as a variable argument template, there is no need to make it a tuple, and tuple + apply shines when you need to “call it all at once” like lazy execution/queuing/serialization.

Reinforcement of actual patterns

  • Settings/CLI parsing: It is easy to manage the argument order in one place by tying the key-value into a tuple and passing it to the verification function bool validate(T...) with apply.
  • SQL Binding·RPC Stub: If the column/field type is fixed to a tuple, you can wrap the procedure call with apply (in reality, the DB API does not support tuples, so unpack it with apply internally to create a bind call).
  • Test Fixture: Representing input cases with std::tuple and calling function under test with apply simplifies data-driven testing.

Connection with metaprogramming

Implementations of std::apply typically use the access of std::index_sequence and std::get<I>. That is, for a tuple whose length is determined at compile time, an expansion call is made without runtime overhead.

The following example demonstrates the concept in cpp:

// Concept: std::get<I>(t)....for 0..N-1 as index_sequence.
// 실행 예제
template<class F, class Tuple, std::size_t....I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}

When used with C++23 std::bind_front / std::invoke_r, etc., it is easy to organize the pattern of passing only the remaining arguments to a partially applied function as a tuple. From a metaprogramming perspective, it is useful to remember apply as “the glue that converts tuple types into function signatures”.

FAQ

Q1: What is a tuple?

A: A container that groups multiple values in C++11. You can save different types.

std::tuple<int, double, std::string> t{42, 3.14, "hello"};

auto [x, y, z] = t;  // C++17 structured binding

Q2: What is apply?

A: A function that unpacks tuples into function arguments in C++17.

int add(int a, int b) { return a + b; }

auto args = std::make_tuple(2, 3);
int result = std::apply(add, args);  // add(2, 3)

Q3: How to unpack tuples?

A:

  • structured binding (C++17): auto [x, y, z] = tuple;
  • tie: std::tie(x, y, z) = tuple;
  • apply: std::apply(func, tuple);

The following example demonstrates the concept in cpp:

std::tuple<int, double, std::string> t{42, 3.14, "hello"};

// structured binding
auto [x, y, z] = t;

// tie
int a;
double b;
std::string c;
std::tie(a, b, c) = t;

// apply
std::apply([](int x, double y, const std::string& z) {
    std::cout << x << ", " << y << ", " << z << '\n';
}, t);

Q4: How do I store references?

A: Use std::forward_as_tuple.

The following example demonstrates the concept in cpp:

int x = 42;

// make_tuple: copy
auto t1 = std::make_tuple(x);

// forward_as_tuple: see
auto t2 = std::forward_as_tuple(x);

std::apply([](int& val) {
    val = 100;
}, t2);

std::cout << x << '\n';  // 100

Q5: What is the performance of apply?

A: Inlineable, so overhead is minimal.

// Compiler optimizes inline
std::apply(add, std::make_tuple(1, 2, 3));
// → add(1, 2, 3) (same as direct call)

Q6: What is make_from_tuple?

A: Creates an object using tuples as constructor arguments.

struct Point {
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
};

auto args = std::make_tuple(10, 20);
auto point = std::make_from_tuple<Point>(args);  // Point(10, 20)

Q7: How do you handle empty tuples?

A: Empty tuples are also possible. Used for functions without arguments.

Here is the func implementation:

void func() {
std::cout << "No arguments\n";
}

std::tuple<> empty;
std::apply(func, empty);  // func()

Q8: What are tuple apply learning resources?

A:

Related posts: tuple, structured-binding, variadic-templates.

One-line summary: std::apply is a C++17 function that unpacks the elements of a tuple into function arguments.


Good article to read together (internal link)

Here’s another article related to this topic.

Practical tips

These are tips that can be applied right away in practice.

Debugging tips

  • If you run into a problem, check the compiler warnings first.
  • Reproduce the problem with a simple test case

Performance Tips

  • Don’t optimize without profiling
  • Set measurable indicators first

Code review tips

  • Check in advance for areas that are frequently pointed out in code reviews.
  • Follow your team’s coding conventions

Practical checklist

This is what you need to check when applying this concept in practice.

Before writing code

  • Is this technique the best way to solve the current problem?
  • Can team members understand and maintain this code?
  • Does it meet the performance requirements?

Writing code

  • Have you resolved all compiler warnings?
  • Have you considered edge cases?
  • Is error handling appropriate?

When reviewing code

  • Is the intent of the code clear?
  • Are there enough test cases?
  • Is it documented?

Use this checklist to reduce mistakes and improve code quality.


Keywords covered in this article (related search terms)

This article will be helpful if you search for C++, tuple, apply, unpack, C++17, etc.



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

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


이 글에서 다루는 키워드 (관련 검색어)

C++, tuple, apply, unpack, C++17 등으로 검색하시면 이 글이 도움이 됩니다.