tuples
, parameter packs (variadic templates), and initializer lists are closely relatedtuple
¶std::tuple
is a generalization of std::pair
tuple
is a standard library component, implemented using parameter packstuple
holds an arbitrary number of elements of arbitrary type (including none){
tuple<> a; // empty
tuple<int> b = 5;
tuple<int, string> c = {5, "Hello World!"s};
tuple<int, string, double> d = {5, "Hello World!"s, 42.5};
}
make_tuple
{
auto x = make_tuple(10, 3.0, "Hello World!"s);
}
{
tuple x = {10, 3.0, "Hello World!"s}; // since C++17
}
get<>()
is used to retrieve an element from a tuple{
tuple x = {10, 3.2, "Hello World!"s};
cout << get<1>(x) << endl;
}
tuple_element_t<>
is used to retrieve an element type from a tuple{
tuple x = { 10, 3.2, "Hello World!"s };
cout << typeid(tuple_element_t<1, decltype(x)>).name() << endl;
}
tuple_size_v<>
is used to get the number of elements in a tuple{
tuple x = { 10, 3.0, "Hello World!"s };
cout << tuple_size_v<decltype(x)> << endl;
}
tie()
function creates a tuple
of l-value referencestie()
to extract the elements of a tuple
{
int a;
string b;
tie(a, b) = tuple{10, "Hello World!"s};
cout << a << ", " << b << endl;
}
ignore
with tie()
to skip any elements{
string a;
tie(ignore, a) = tuple{10, "Hello World!"s};
cout << a << endl;
}
{
auto [a, b] = tuple{10, "Hello World!"s};
cout << a << ", " << b << endl;
}
ignore
equivalenttie()
is also useful for class reflectiontuple
provides lexicographical comparisonsclass example1 {
int _a;
string _b;
bool _c;
auto as_tuple() const { return tie(_a, _b, _c); }
public:
example1(int a, string b, bool c) : _a(move(a)), _b(move(b)), _c(move(c)) { }
friend inline bool operator==(const example1& x, const example1& y) {
return x.as_tuple() == y.as_tuple();
}
friend inline bool operator<(const example1& x, const example1& y) {
return x.as_tuple() < y.as_tuple();
}
//...
};
{
example1 x(10, "Hello", false);
example1 y(10, "World", false);
cout << boolalpha;
cout << "x == x: " << (x == x) << endl;
cout << "x == y: " << (x == y) << endl;
cout << "x < y: " << (x < y) << endl;
cout << "y < x: " << (y < x) << endl;
cout << "x < x: " << (x < x) << endl;
}
template <class... Args> // Args is a type parameter pack
void example_fn1(Args... args); // Args... is a pack expansion
// args is a function parameter pack
Args
would be a tuple type and args
a tuple instance that is not the casetemplate <class... Args>
void example_fn1(Args... args) {
auto x = args;
}
input_line_26:3:14: error: initializer contains unexpanded parameter pack 'args'
auto x = args;
^~~~
...
template <class... Args>
void example_fn2(Args... args) {
tuple x = { args... }; // expand parameter pack into a tuple
}
arg1, arg2, arg3, ...
// A C++14 implementation of make_tuple()
template <class... Args>
auto make_tuple1(
Args&&... args) { // type parameter pack expansion to forward references
return tuple<decay_t<Args>...>(
forward<Args>(args)...); // type and function parameter pack expansions
}
{
auto [a, b, c] = make_tuple1(10, "Hello", false);
cout << a << ", " << b << ", " << c << endl;
}
template <class T, class... Args>
auto sum(T&& initial, Args&&... args) {
return (forward<T>(initial) + ... + forward<Args>(args));
}
{
cout << sum(1, 3, 5) << endl;
cout << sum("Hello"s, " ", "Class!") << endl;
}
template <int... Ns>
constexpr int sum() {
return (... + Ns);
}
{
cout << sum<1, 2, 3, 4>() << endl;
}
auto...
can be used to create a function parameter pack in a lambda{
auto product = [](auto... args) { return (args * ...); };
cout << product(1, 3, 5) << endl;
}
type...
to get a non-type parameter pack{
auto product = [](int... args){ return (args * ...); };
}
input_line_52:3:26: error: type 'int' of function parameter pack does not contain any unexpanded parameter
packs
auto product = [](int... args){ return (args * ...); };
sizeof...()
will tell you the number of elements in a parameter packtemplate <class... Args>
size_t arg_count(const Args&... args) {
return sizeof...(args);
}
{
cout << arg_count(1, 32.5, "Hello") << endl;
}
std::apply()
converts a tuple into arguments to a function{
tuple x = { "Hello!"s, 3 };
apply([](const string& str, int n){
while (n-- != 0) cout << str << endl;
}, x);
}
apply()
to convert a tuple into an argument pack{
tuple x = {1, 3, 5, 7, 9};
cout << apply([](auto... args) { return (... + args); }, x) << endl;
}
initializer_list<>
¶std::initializer_list<>
it may be passed a list of elements of the same type{
auto product = [](initializer_list<int> args) {
// should use reduce but not implemented in libstdc++
// return (args * ...);
return accumulate(begin(args), end(args), 1, multiplies());
};
cout << product({1, 3, 5}) << endl;
}
{
vector v = {0, 10, 20, 30};
for (const auto& e : v) cout << e << endl;
}
An initializer_list<>
differs from a parameter pack in a few ways
initializer_list<>
can only be a single typeinitializer_list<>
is a temporary object, access to it is always via const &
initializer_list<>
initializer_list<>
are allowed to be stored in read-only memoryinitializer_list<>
is defined to be left-to-rightinitializer_list<>
does not require a template interfaceinitializer_list<>
can be used with a range based for loopAn initializer_list
is not a library only feature, it is a language feature exposed through a library interface
{
auto a = { "Hello"s, "World!"s }; // a is initializer_list<string>
for (const auto& e : a) cout << e << endl;
}
{
auto a = { "Hello"s, 10 };
}
input_line_47:3:10: error: cannot deduce actual type for variable 'a' with type
'auto' from initializer list
auto a = { "Hello"s, 10 };
^ ~~~~~~~~~~~~~~~~
template <class F, class... Args>
constexpr F for_each_argument(F f, Args&&... args) {
(void)std::initializer_list<int>{(f(std::forward<Args>(args)), 0)...};
return f;
}
{
for_each_argument([](const auto& e){
cout << e << endl;
}, 10, "Hello!", 35.2);
}
apply()
to convert a tuple to an argument listfor_each_argument()
we can iterate over a tuplenamespace {
template <class F, class Tuple>
constexpr F for_each_element(F f, Tuple&& t) {
return std::apply(
[_f = std::move(f)](auto&&... args) {
return for_each_argument(std::move(_f),
std::forward<decltype(args)>(args)...);
},
std::forward<Tuple>(t));
}
} // namespace
{
for_each_element([](const auto& e){
cout << e << endl;
}, tuple(10, "Hello!"s, 35.2));
}
tie()
to reflect a object members into a tuple, we can iterate the members of the objectnamespace {
class example2 {
int _a;
string _b;
bool _c;
auto as_tuple() const { return tie(_a, _b, _c); }
public:
example2(int a, string b, bool c) : _a(move(a)), _b(move(b)), _c(move(c)) { }
friend inline ostream& operator<<(ostream& out, const example2& x) {
for_each_element([&](const auto& e){
out << boolalpha << e << endl;
}, x.as_tuple());
return out;
}
};
} // namespace
{
example2 x(42, "Hello World", true);
cout << x;
}
namespace {
template <class>
class task;
template <class R, class... Args>
class task<R(Args...)> {
struct concept;
template <class F>
struct model;
unique_ptr<concept> _p;
public:
constexpr task() noexcept = default;
template <class F>
task(F&& f) : _p(make_unique<model<decay_t<F>>>(forward<F>(f))) {}
task(task&&) noexcept = default;
task& operator=(task&&) noexcept = default;
R operator()(Args... args) { return _p->invoke(forward<Args>(args)...); }
};
} // namespace
namespace {
template <class R, class... Args>
struct task<R(Args...)>::concept {
virtual ~concept() = default;
virtual R invoke(Args&&...) = 0;
};
template <class R, class... Args>
template <class F>
struct task<R(Args...)>::model final : concept {
template <class G>
explicit model(G&& f) : _f(forward<G>(f)) {}
R invoke(Args&&... args) override { return move(_f)(forward<Args>(args)...); }
F _f;
};
} // namespace
{
task<string(int)> f;
f = [_prefix = "Hello "s](int suffix) mutable {
return move(_prefix) + to_string(suffix);
};
cout << f(5) << endl;
}
template <class F, class... Args>
auto bind_all_1(F f, Args&&... args) {
return [_f = move(f), args...] { _f(args...); };
}
{
auto print =
bind_all_1([](const string& a, const string& b) { cout << a << ", " << b; },
"Hello", "World!");
print();
}
{
auto print = bind_all_1([](const instrumented& a, const instrumented& b) {}, instrumented(),
instrumented());
print();
}
template <class F, class... Args>
auto bind_all_2(F f, Args&&... args) {
return
[_f = move(f), _args = tuple{forward<Args>(args)...}] { apply(_f, _args); };
}
{
auto print = bind_all_2([](const instrumented& a, const instrumented& b) {}, instrumented(),
instrumented());
print();
}
tie()
is a useful tool for compile time reflectionstd::async(&f, a, b);
vs std::async([=]{ f(a, b); });
initializer_list<>
is troublesome because it doesn't support move, only use when the type is known to be trivialinitializer_list<>
is rarely useful outside of a test casetuple
where a struct
would be more clear