I am writing a function in C++ with a variable number of arguments (and different types) in this way
template<typename ...Ts>
void myFunction(Ts ...args)
{
//create std::tuple to access and manipulate single elements of the pack
auto myTuple = std::make_tuple(args...);
//do stuff
return;
}
What i would like to do, but I don't know how, is to push and pop elements from the tuple, in particular the first element... something like
//remove the first element of the tuple thereby decreasing its size by one
myTuple.pop_front()
//add addThis as the first element of the tuple thereby increasing its size by one
myTuple.push_front(addThis)
Is this possible?
You may do something like
template <typename T, typename Tuple>
auto push_front(const T& t, const Tuple& tuple)
{
return std::tuple_cat(std::make_tuple(t), tuple);
}
template <typename Tuple, std::size_t ... Is>
auto pop_front_impl(const Tuple& tuple, std::index_sequence<Is...>)
{
return std::make_tuple(std::get<1 + Is>(tuple)...);
}
template <typename Tuple>
auto pop_front(const Tuple& tuple)
{
return pop_front_impl(tuple,
std::make_index_sequence<std::tuple_size<Tuple>::value - 1>());
}
Demo
Note that it is really basic and doesn't handle tuple of reference, or tuple of const qualified type, but it might be sufficient.
With generic lambdas, you can do quite elegant:
template<typename Tuple>
constexpr auto pop_front(Tuple tuple) {
static_assert(std::tuple_size<Tuple>::value > 0, "Cannot pop from an empty tuple");
return std::apply(
[](auto, auto... rest) { return std::make_tuple(rest...); },
tuple);
}
Length and types of a std::tuple are determined at compile time. No run-time popping or pushing is possible. You could use a std::vector to provide for the run-time modifications.
The data type for your vector could be a std::variant (C++17) or boost::variant. Both types take a list of supported types at compile time, and can be filled with any value of matching type.
Alternatively, you could use std::any (also C++17) or boost::any to store any type, but with different access semantics.
typedef boost::variant<int, std::string, double> value;
std::vector<value> data;
data.push_back(value(42));
data.psuh_back(value(3.14));
You cannot 'lengthen' the tuple by adding elements - that is not what a tuple represents. The point of a tuple is the binding connection between the different values that make it up, like 'firstname', 'lastname', 'phone'.
What you seem to want is much easier accomplished by using a vector - you can easily add elements to it, and remove them - its size can be arbitrarily changed as needed.
Related
I have two tuples like this
std::tuple<std::vector<int>, std::vector<int>> t1;
std::tuple<std::vector<int>, std::vector<int>> t2;
I now want to concat the entries of the tuples (so that I have one tuple containing two vectors with the entries of the the first/second vectors of the tuples). It is fine if the tuple is mutated.
I can do this like this:
std::get<0>(t1).insert(std::get<0>(t1).end(), std::get<0>(t2).begin(), std::get<0>(t2).end());
for each entry, but if i have a lot if entries in the tuple, it becomes very ugly.
Iterating the tuple with a normal for loop does not work since std::get requires a constant. I did not get it to work with std::apply because only one argument can be passed.
Use nested fold-expression, one to expand the tuples and one to expand the elements of the tuple
#include <tuple>
#include <vector>
template<class FirstTuple, class... RestTuples>
void concatenate(FirstTuple& first, const RestTuples&... rest) {
constexpr auto N = std::tuple_size_v<FirstTuple>;
static_assert(((N == std::tuple_size_v<RestTuples>) && ...));
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
([&](const auto& other) {
(std::get<Is>(first).insert(
std::get<Is>(first).end(),
std::get<Is>(other).begin(),
std::get<Is>(other).end()), ...);
}(rest), ...);
}(std::make_index_sequence<N>{});
}
Demo
For a more concise answer, see what 康桓瑋 suggests.
In case you don't have access to C++20's templated lambda expressions, here is the code that uses C++17's features.
First, I would like to produce a new tuple that will consist of merged entries, that is:
auto t1 = std::tuple<std::vector<int>, std::vector<int>>{
{1, 2, 3}, {7, 8}
};
auto t2 = std::tuple<std::vector<int>, std::vector<int>>{
{4, 5, 6}, {9}
};
auto merged = merge_inner(t1, t2);
Thus, merge_inner needs to accept any number of said tuples:
template <
template <typename> typename Tuple,
template <typename> typename... Tuples,
typename... Vectors
>
auto merge_inner(Tuple<Vectors...> first, Tuples<Vectors...> const&... args) {
constexpr auto number_of_vectors = sizeof...(Vectors);
append_values_elementwise(first, std::make_index_sequence<number_of_vectors>(), args...);
return first;
}
We copy the first argument and take the rest by const&. To all vectors (elements) of first, we append those which are inside args... by calling append_values_elementwise. I used std::index_sequence in order to help fold-expressions making my life a little easier:
template <
template <typename> typename Tuple,
std::size_t... Ns,
template <typename> typename... Tuples,
typename... Vectors
>
auto append_values_elementwise(
Tuple<Vectors...>& first,
std::index_sequence<Ns...> sequence,
Tuple<Vectors...> const& first_arg,
Tuples<Vectors...> const&... args
) {
((std::get<Ns>(first).insert(
std::get<Ns>(first).end(),
std::get<Ns>(first_arg).begin(), std::get<Ns>(first_arg).end()
)), ...);
append_values_elementwise(first, sequence, args...);
}
This is just an implementation detail that extracts the next tuple from args... and inserts the elements of its vectors into first. sequence represents a meta-list of indexes suitable to be used with std::get. This is a metaprogramming way of looping over tuples values.
We then recurse with the rest of the arguments.
Lastly, we need a fallback to when we only have one tuple left to merge. The terminal case, which no longer recurses:
template <
template <typename> typename Tuple,
std::size_t... Ns,
template <typename> typename... Tuples,
typename... Vectors
>
auto append_values_elementwise(
Tuple<Vectors...>& first,
std::index_sequence<Ns...>,
Tuple<Vectors...> const& first_arg
) {
((std::get<Ns>(first).insert(
std::get<Ns>(first).end(),
std::get<Ns>(first_arg).begin(), std::get<Ns>(first_arg).end()
)), ...);
}
Demo.
I found this implementation of a few common features of functional programming, e.g. map / reduce:
(I'm aware stuff like that is aparently coming or partially present in new C++ versions)
github link
A part of the code:
template <typename T, typename U>
U foldLeft(const std::vector<T>& data,
const U& initialValue,
const std::function<U(U,T)>& foldFn) {
typedef typename std::vector<T>::const_iterator Iterator;
U accumulator = initialValue;
Iterator end = data.cend();
for (Iterator it = data.cbegin(); it != end; ++it) {
accumulator = foldFn(accumulator, *it);
}
return accumulator;
}
template <typename T, typename U>
std::vector<U> map(const std::vector<T>& data, const std::function<U(T)> mapper) {
std::vector<U> result;
foldLeft<T, std::vector<U>&>(data, result, [mapper] (std::vector<U>& res, T value) -> std::vector<U>& {
res.push_back(mapper(value));
return res;
});
return result;
}
Usage example:
std::vector<int> biggerInts = map<int,int>(test, [] (int num) { return num + 10; });
The type arguments T,U have to be fully qualified for this to compile, as shown in the example, with e.g. map< int,int >( ... ).
This implementation is for C++11, as mentioned on the linked-to page.
Is it possible with newer C++ versions (or even 11) now to make the use of this less verbose, i.e. making the types U,T deduce automatically?
I have googled for that and only found that there is apparently some improvement for class template, as opposed to function template, argument deduction in C++17.
But since I only ever used templates in a rather basic manner, I was wondering whether there is something in existence that I'm not aware of which could improve this implementation verboseness-wise.
You can rewrite map signature to be:
template <typename T, typename M, typename U = decltype(std::declval<M>()(T{}))>
std::vector<U> map(const std::vector<T>& data, const M mapper)
then T will be deduced as value_type of vector's items.
M is any callable object.
U is deduced as return type of M() functor when called for T{}.
Below
std::vector<int> biggerInts = map(test, [] (int num) { return num + 10; });
^^^^ empty template arguments list
works fine.
Live demo
More general templates make template argument deduction easier.
One principle: it is often a mistake to use a std::function as a templated function's parameter. std::function is a type erasure, for use when something needs to store some unknown invokable thing as a specific type. But templates already have the ability to handle any arbitrary invokable type. So if we just use a generic typename FuncT template parameter, it can be deduced for a raw pointer-to-function, a lambda, or another class with operator() directly.
We might as well also get more general and accept any input container instead of just vector, then determine T from it, if it's even directly needed.
So for C++11 I would rewrite these:
// C++20 is adding std::remove_cvref, but it's trivial to implement:
template <typename T>
using remove_cvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <typename Container, typename U, typename FuncT>
remove_cvref_t<U> foldLeft(
const Container& data,
U&& initialValue,
const FuncT& foldFn) {
remove_cvref_t<U> accumulator = std::forward<U>(initialValue);
for (const auto& elem : data) {
accumulator = foldFn(std::move(accumulator), elem);
}
return accumulator;
}
template <typename Container, typename FuncT>
auto map(const Container& data, const FuncT& mapper)
-> std::vector<remove_cvref_t<decltype(mapper(*std::begin(data)))>>
{
using T = remove_cvref_t<decltype(*std::begin(data))>;
using ResultT = std::vector<remove_cvref_t<decltype(mapper(std::declval<const T&>()))>>;
ResultT result;
foldLeft(data, std::ref(result), [&mapper] (ResultT &res, const T& value) -> ResultT& {
res.push_back(mapper(value));
return res;
});
return result;
}
See the working program on coliru.
There was one unfortunate thing about the old map: it potentially copied the result vector at every iteration. The = in accumulator = foldFn(accumulator, *it); is a self-assignment, which might do nothing, or might allocate new memory, copy contents, then free the old memory and update the container. So instead I've changed the U for foldLeft in this case to a std::reference_wrapper. The = in that case will still "rebind" the wrapper to the same object, but that will at least be quick.
In C++14 and later, you could do away with finding T within map by using a generic lambda: [&mapper] (std::vector<U>& res, const auto& value) ...
I am new to C++ and doing a class in it now. As homework, we are supposed to write a function that accepts any container as input but does not use iterators. So we are not allowed to just pass std::begin(container) and std::end(container) as arguments. We have to pass the containers as references. My take on this was the following function declaration:
template <typename T, typename U, typename V, template <typename a, typename b> typename container>
void doStuff(container<T, U>& first, container<T, V>& second)
{
// the code
}
It accepts two containers (or any templated type that uses two template arguments). The second template argument in container is different since in arrays V might represent the size of the array and I want to be able to accept two arrays of different sizes.
Example:
std::array<bool, 4> a1 = { true, false, false, false };
std::array<bool, 1> a2 = { false };
Unfortunately, this example does not work. The error says that I doStuff does not accept arguments of these types. Why is that?
In my opinion, using a "templated template" here is important, because I would like to make sure that the function only accepts two containers if they contain the same kind of data. So passing an int array and a double array should not work.
If you want to support as many container types as possible, you should use the least restricting definition possible:
template <typename FirstContainer, typename SecondContainer>
void doStuff(FirstContainer& first, SecondContainer& second)
{
}
This is because container types come in all sort of flavors, e.g. std::array is a template taking a value type and a static size as argument, std::vector is a template taking a value type and an allocator, some custom StringList might not be a template at all.
Your implementation might have some specific requirements to the supported containers, e.g. it might only work for containers of integer types. But more often than not this is just an implicit result of the implementation. Take for a example a simple function summing up values of two containers:
template <typename FirstContainer, typename SecondContainer>
int sum(const FirstContainer& first, const SecondContainer& second)
{
int result = 0;
for (auto value : first)
result += value;
for (auto value : second)
result += value;
return result;
}
This function works fine with any value types that can be added to an integer. If it can't be added (like a std::string), it will cause a compilation error eventually.
(Note that one could write this function even more generic with an automatically deduced sum type instead of just int)
If these "implicit requirements" are not enough for you, you can add explicit checks using static_assert:
template <typename FirstContainer, typename SecondContainer>
int sum(const FirstContainer& first, const SecondContainer& second)
{
int result = 0;
for (auto value : first)
{
static_assert(std::is_same_v<int, decltype(value)>, "FirstContainer does not hold integers");
result += value;
}
for (auto value : second)
{
static_assert(std::is_same_v<int, decltype(value)>, "SecondContainer does not hold integers");
result += value;
}
return result;
}
Now your function is only accepting containers holding plain int, nothing else.
You can also use std::enable_if to completely "disable" your function for non-supported containers:
template <typename FirstContainer, typename SecondContainer>
auto sum(const FirstContainer& first, const SecondContainer& second)
-> std::enable_if_t<
std::is_same_v<typename FirstContainer::value_type, int> &&
std::is_same_v<typename SecondContainer::value_type, int>,
int
>
{
int result = 0;
for (auto value : first)
result += value;
for (auto value : second)
result += value;
return result;
}
Now your function is restricted to containers with a nested typedef value_type to type int.
In the simple parser library I am writing, the results of multiple parsers is combined using std::tuple_cat. But when applying a parser that returns the same result multiple times, it becomes important to transform this tuple into a container like a vector or a deque.
How can this be done? How can any tuple of the kind std::tuple<A>, std::tuple<A, A>, std::tuple<A, A, A> etc be converted into a std::vector<A>?
I think this might be possible using typename ...As and sizeof ...(As), but I am not sure how to create a smaller tuple to call the function recursively. Or how to write an iterative solution that extracts elements from the tuple one by one. (as std::get<n>(tuple) is constructed at compile-time).
How to do this?
With the introduction of std::apply(), this is very straightforward:
template <class Tuple,
class T = std::decay_t<std::tuple_element_t<0, std::decay_t<Tuple>>>>
std::vector<T> to_vector(Tuple&& tuple)
{
return std::apply([](auto&&... elems){
return std::vector<T>{std::forward<decltype(elems)>(elems)...};
}, std::forward<Tuple>(tuple));
}
std::apply() is a C++17 function but is implementable in C++14 (see link for possible implementation). As an improvement, you could add either SFINAE or a static_assert that all the types in the Tuple are actually T.
As T.C. points out, this incurs an extra copy of every element, since std::initializer_list is backed by a const array. That's unfortunate. We win some on not having to do boundary checks on every element, but lose some on the copying. The copying ends up being too expensive, an alternative implementation would be:
template <class Tuple,
class T = std::decay_t<std::tuple_element_t<0, std::decay_t<Tuple>>>>
std::vector<T> to_vector(Tuple&& tuple)
{
return std::apply([](auto&&... elems) {
using expander = int[];
std::vector<T> result;
result.reserve(sizeof...(elems));
expander{(void(
result.push_back(std::forward<decltype(elems)>(elems))
), 0)...};
return result;
}, std::forward<Tuple>(tuple));
}
See this answer for an explanation of the expander trick. Note that I dropped the leading 0 since we know the pack is non-empty. With C++17, this becomes cleaner with a fold-expression:
return std::apply([](auto&&... elems) {
std::vector<T> result;
result.reserve(sizeof...(elems));
(result.push_back(std::forward<decltype(elems)>(elems)), ...);
return result;
}, std::forward<Tuple>(tuple));
Although still relatively not as nice as the initializer_list constructor. Unfortunate.
Here's one way to do it:
#include <tuple>
#include <algorithm>
#include <vector>
#include <iostream>
template<typename first_type, typename tuple_type, size_t ...index>
auto to_vector_helper(const tuple_type &t, std::index_sequence<index...>)
{
return std::vector<first_type>{
std::get<index>(t)...
};
}
template<typename first_type, typename ...others>
auto to_vector(const std::tuple<first_type, others...> &t)
{
typedef typename std::remove_reference<decltype(t)>::type tuple_type;
constexpr auto s =
std::tuple_size<tuple_type>::value;
return to_vector_helper<first_type, tuple_type>
(t, std::make_index_sequence<s>{});
}
int main()
{
std::tuple<int, int> t{2,3};
std::vector<int> v=to_vector(t);
std::cout << v[0] << ' ' << v[1] << ' ' << v.size() << std::endl;
return 0;
}
Although, this doesn't answer the question completely, This still might be suitable in some cases. Only when the number of elements in tuple is around 5, 6. (And you know the size).
tuple<int, int, int, int> a = make_tuple(1, 2, 3, 4);
auto [p, q, r, s] = a;
vector<int> arr(p, q, r, s); // Now, arr has the same elements as in tuple a
Note that, this is C++ 17 feature. More info here
Suppose I have a std::vector of a size known at compile time, and I want to turn that into an std::array. How would I do that? Is there a standard function to do this?
The best solution I have so far is this:
template<class T, std::size_t N, class Indexable, std::size_t... Indices>
std::array<T, N> to_array_1(const Indexable& indexable, std::index_sequence<Indices...>) {
return {{ indexable[Indices]... }};
}
template<class T, std::size_t N, class Indexable>
std::array<T, N> to_array(const Indexable& indexable) {
return to_array_1<T, N>(indexable, std::make_index_sequence<N>());
}
std::array<Foo, 123> initFoos {
std::vector<Foo> lst;
for (unsigned i = 0; i < 123; ++i)
lst.push_back(getNextFoo(lst));
return to_array<Foo, 123>(lst); // passing lst.begin() works, too
}
The application is similar to Populate std::array with non-default-constructible type (no variadic templates): I too have a type which is not default-constructible, so I need to compute the actual values by the time the array gets initialized. However contrary to that question, for me the values are not merely a function of the index, but also of the preceding values. I can build my values much more easily using a loop than a series of function calls. So I construct the elements in a loop and place them in a vector, then I want to use the final state of that vector to initialize the array.
The above seems to compile and work fine, but perhaps there are ways to improve it.
Perhaps I could make clever use of some standard library functionality that I wasn't aware of.
Perhaps I can avoid the helper function somehow.
Perhaps I can somehow formulate this in such a way that it works with move semantics for the elements instead of the copy semantics employed above.
Perhaps I can avoid the random access using operator[], and instead use forward iterator semantics only so it would work for std::set or std::forward_list as input, too.
Perhaps I should stop using std::vector and instead express my goal using std::array<std::optional<T>, N> instead, using C++17 or some equivalent implementation.
Related questions:
Copy std::vector into std::array which doesn't assume a type without default constructor, so copying after default initialization is feasible there but not for me.
Populate std::array with non-default-constructible type (no variadic templates) which computes each element from its index independently, so it tries to avoid an intermediate container. Answers use variadic templates even though the title requests avoiding them.
I would propose:
template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
return {{ ((void)Is, *iter++)... }};
}
template<std::size_t N, typename Iter,
typename T = typename std::iterator_traits<Iter>::value_type>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
return to_array<T>(iter, std::make_index_sequence<N>{});
}
This deduces the element type from the iterator and leaves copy-vs-move semantics up to the caller – if the caller wants to move, they can opt-in via std::move_iterator or the like:
auto initFoos() {
constexpr unsigned n{123};
std::vector<Foo> lst;
for (unsigned i{}; i != n; ++i) {
lst.push_back(getNextFoo(lst));
}
// copy-init array elements
return to_array<n>(lst.cbegin());
// move-init array elements
return to_array<n>(std::make_move_iterator(lst.begin()));
}
Online Demo
EDIT: If one wants to override the deduced element type, as indicated in the comments, then I propose:
template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
return {{ ((void)Is, T(*iter++))... }};
}
template<std::size_t N, typename U = void, typename Iter,
typename V = typename std::iterator_traits<Iter>::value_type,
typename T = std::conditional_t<std::is_same<U, void>{}, V, U>>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
return to_array<T>(iter, std::make_index_sequence<N>{});
}
This leaves the element type optional but makes it the second parameter rather than the first, so usage would look like to_array<N, Bar>(lst.begin()) rather than to_array<Bar, N>(lst.begin()).