Expand parameter pack in std::function template - c++

I'm working on some shorthand functional programming methods to aid in data analysis in C++ and I ran into a situation where I feel like my implmentation should work but g++ disagrees with me. See the following code:
#include <algorithm>
#include <valarray>
#include <functional>
#include <iostream>
using namespace std;
//generates a list of [from,to] in increments of step. last is <= to with precision of step
template<typename T> std::valarray<T> range(T from, T to, T step = 1) {
size_t elems = (size_t)floor((to-from)/step) + 1;
std::valarray<T> result(elems);
for (int i = 0; i < elems; i++) {
result[i] = from+step*i;
}
return result;
}
//map over multiple lists as arguments to the provided function
template<typename T, typename... Ts> void mapthreadv(std::function<void(T,Ts...)> func, std::valarray<T> &in, std::valarray<Ts>&... rest) {
for (int i = 0; i < in.size(); i++) {
func(in[i],rest[i]...);
}
}
int main(int argc, char **argv) {
auto first = range(0.0,1.0,0.1);
auto second = range(0.0,10.0,1.0);
auto third = range(0.0,100.0,10.0);
mapthreadv<double,double,double>([](double a, double b, double c) { cout << '{' << a << ',' << b << ',' << c << "},"; },first,second,third);
}
Expected output would be:
{0,0,0},{0.1,1,10},{0.2,2,20},{0.3,3,30},{0.4,4,40},{0.5,5,50},{0.6,6,60},{0.7,7,70},{0.8,8,80},{0.9,9,90},{1,10,100},
Which is achievable by directly specifying <void(double,double,double)> instead of <void(T,Ts...)> to std::function, however this is obviously not a useful fix. The code fails to compile as written, and the error is related to template argument deduction/substitution:
‘main(int, char**)::<lambda(double, double, double)>’ is not derived from ‘std::function<void(double, Ts ...)>’
So my gut feeling is that for some reason Ts is not being expanded... any pointers or obvious oversights on my part? I'm quite new to template functions in general so any help is appreciated.

The problem is that template argument deduction is still performed when you use a template parameter pack, even if you explicitly specify the types (§ 14.8.1 [temp.arg.explicit]/p9):
Template argument deduction can extend the sequence of template
arguments corresponding to a template parameter pack, even when the
sequence contains explicitly specified template arguments. [
Example:
template<class ... Types> void f(Types ... values);
void g() {
f<int*, float*>(0, 0, 0);
}
// Types is deduced to the sequence int*, float*, int
— end example ]
And, since a lambda closure type is not a std::function, template argument deduction will fail.
There is no reason to use std::function here anyway; you can simply take the functor as a template parameter:
template<typename F, typename T, typename... Ts> void mapthreadv(F func, std::valarray<T> &in, std::valarray<Ts>&... rest) {
for (int i = 0; i < in.size(); i++) {
func(in[i],rest[i]...);
}
}
which also obviates the need to explicitly specify template arguments:
mapthreadv([](double a, double b, double c) { std::cout << '{' << a << ',' << b << ',' << c << "},"; },first,second,third);
Demo.

Related

Calculate the sum of all arguments(non-type parameters) passed through a template

I want to calculate the sum of all arguments (non-type parameters) passed through a template. I compiled the following program with: g++ -std=c++17 -g -Wall -o main main.cpp. It seems that I miss something, because I get this errors when compiling:
error: call of overloaded ‘func<N_0>()’ is ambiguous
std::cout << func<N_0>() << std::endl;
error: call of overloaded ‘func<22>()’ is ambiguous
return N + func<Next... >();
#include <iostream>
using namespace std;
template <std::size_t T>
std::size_t sum()
{
return T;
}
template <std::size_t N, std::size_t ...Next>
std::size_t sum()
{
return N + sum<Next... >();
}
int main()
{
const size_t N_0 = 4;
const size_t N_1 = 11;
const size_t N_2 = 22;
std::cout << sum<N_0>() << std::endl;
std::cout << sum<N_0, N_1, N_2>() << std::endl;
return 0;
}
I found a lot of examples like this:
#include <iostream>
template<typename T>
T adder(T first) {
return first;
}
template<typename T, typename... Args>
T adder(T first, Args... args) {
return first + adder(args...);
}
int main() {
const int c = adder(1, 8, 4);
std::cout << c << '\n';
return 0;
}
I am very curious why my code is not working.
In both the first and second code example, calling the function with only one (template) argument results in both function templates being viable. The packs will simply be empty.
However, in overload resolution in the second example the variadic template is considered less specialized than the non-variadic one, basically because the set of possible arguments to call it with is a superset of those that the non-variadic one can be called with. This is only because of the parameter pack in the function parameters. Therefore overload resolution will prefer the non-variadic template as a tie-breaker.
This ordering doesn't apply to your first code example, where the function parameters of both templates are the same.
Therefore overload resolution with a single (template) argument is ambiguous in your first code example, but not ambiguous in the second.
You can specify that the variadic template requires at least two arguments to resolve the ambiguity:
template <std::size_t N, std::size_t M, std::size_t ...Next>
std::size_t sum()
{
// May wrap-around to zero
return N + sum<M, Next... >();
}
However, in C++17 and later the whole sum construct can be simplified by use of fold expressions to:
template <std::size_t ...Ns>
std::size_t sum()
{
// May wrap-around to zero
return (Ns + ...);
}
(This function should probably be marked noexcept and constexpr in C++17 or consteval in C++20 and one should be careful with many/large arguments since the addition will silently wrap-around if the sum becomes too large for std::size_t to hold.)

Can I change the template argument deduction order for a generic variadic lambda?

Take the following code, which is a simplified example:
template <typename F>
void foo(F f) {
//bool some = is_variadic_v<F>; // Scenario #1
bool some = true; // Scenario #2
f(int(some), int(some));
}
int main() {
auto some = [](int i, int j) {
std::cout << i << " " << j << '\n';
};
foo([&some](auto... params) {
some(params...);
});
}
A function takes a generic variadic lambda and calls it with a fixed set of arguments. This lambda itself then just calls another function/lambda with a matching prototype.
As one could expect, in scenario 2, when f is called inside foo, the compiler will deduce params... to be the parameter pack {1, 1}.
For scenario #1, I am using a code from another Q&A to deduce the arity of a callable object. If however such an object is callable with more than a pre-defined maximum amount of arguments, it is considered "variadic". In detail, is_variadic_v will employ a form of expression SFINAE where it is attempted to call the function object with a decreasing number of arguments having an "arbitrary type" that is implictly convertible to anything.
The problem is now that apparently, the compiler will deduce F (and along its argument pack) during this metacode, and if it is variadic (such as in this case), it deduces F as a lambda taking the dummy arguments, i.e. something like main()::lambda(<arbitrary_type<0>, arbitrary_type<1>, arbitrary_type<2>, ..., arbitrary_type<N>>) if N is the "variadic limit" from above. Now params... is deduced as arbitrary_type<1>, arbitrary_type<2>, ... and correspondingly, the call some(params...) will fail.
This behaviour can be demonstrated in this little code example:
#include <utility>
#include <type_traits>
#include <iostream>
constexpr int max_arity = 12; // if a function takes more arguments than that, it will be considered variadic
struct variadic_type { };
// it is templated, to be able to create a
// "sequence" of arbitrary_t's of given size and
// hence, to 'simulate' an arbitrary function signature.
template <auto>
struct arbitrary_type {
// this type casts implicitly to anything,
// thus, it can represent an arbitrary type.
template <typename T>
operator T&&();
template <typename T>
operator T&();
};
template <
typename F, auto ...Ints,
typename = decltype(std::declval<F>()(arbitrary_type<Ints>{ }...))
>
constexpr auto test_signature(std::index_sequence<Ints...> s) {
return std::integral_constant<int, size(s)>{ };
}
template <auto I, typename F>
constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{ })) {
return { };
}
template <auto I, typename F, typename = std::enable_if_t<(I > 0)>>
constexpr auto arity_impl(...) {
// try the int overload which will only work,
// if F takes I-1 arguments. Otherwise this
// overload will be selected and we'll try it
// with one element less.
return arity_impl<I - 1, F>(0);
}
template <typename F, auto MaxArity>
constexpr auto arity_impl() {
// start checking function signatures with max_arity + 1 elements
constexpr auto tmp = arity_impl<MaxArity+1, F>(0);
if constexpr (tmp == MaxArity+1)
return variadic_type{ }; // if that works, F is considered variadic
else return tmp; // if not, tmp will be the correct arity of F
}
template <typename F, auto MaxArity = max_arity>
constexpr auto arity(F&&) { return arity_impl<std::decay_t<F>, MaxArity>(); }
template <typename F, auto MaxArity = max_arity>
constexpr auto arity_v = arity_impl<std::decay_t<F>, MaxArity>();
template <typename F, auto MaxArity = max_arity>
constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_type>;
template <typename F>
void foo(F f) {
bool some = is_variadic_v<F>;
//bool some = true;
f(int(some), int(some));
}
int main() {
auto some = [](int i, int j) {
std::cout << i << " " << j << '\n';
};
foo([&some](auto... params) {
some(params...);
});
}
Can I prevent this behaviour? Can I force the compiler to re-deduce the parameter list?
EDIT:
An additional peculiarity is that the compiler seems to act kind of schizophrenic. When I change the contents of foo to
foo([&some](auto... params) {
// int foo = std::index_sequence<sizeof...(params)>{ };
std::cout << sizeof...(params) << '\n';
});
the compiler will create a program that will print 2 in this example. If however I include the commented line (which, as it makes no sense, should trigger a compiler diagnostic), I get confronted with
error: cannot convert 'std::index_sequence<13>' {aka 'std::integer_sequence<long unsigned int, 13>'} to 'int' in initialization
85 | int foo = std::index_sequence<sizeof...(params)>{ };
so does the compiler now deduces sizeof...(params) to be 2 and 13 at the same time? Or did he change his mind and chooses now 13 just because I added another statement into the lambda? Compilation will also fail if I instead choose a static_assert(2 == sizeof...(params));. So the compiler deduces sizeof...(params) == 2, except if I ask him whether he did deduce 2, because then he didn't.
Apparently, it is very decisive for the parameter pack deduction what is written inside the lambda. Is it just me or does this behaviour really look pathologic?

How to use generic function in c++?

I have written this code in which if I uncomment the 2nd last line I get error - "template argument deduction/substitution failed: ". Is it because of some limit to generic functions in C++? Also my program doesn't print floating answer for the array b. Is there anything I can do for that? (sorry for asking 2 questions in single post.)
P.S: I have just started learning C++.
#include <iostream>
using namespace std;
template <class T>
T sumArray( T arr[], int size, T s =0)
{
int i;
for(i=0;i<size;i++)
{ s += arr[i];
}
return s;
}
int main()
{
int a[] = {1,2,3};
double b[] = {1.0,2.0,3.0};
cout << sumArray(a,3) << endl;
cout << sumArray(b,3) << endl;
cout << sumArray(a,3,10) << endl;
//cout << sumArray(b,3,40) << endl; //uncommenting this line gives error
return 0;
}
EDIT 1: After changing 40 to 40.0, the code works. Here is the output I get:
6
6
16
46
I still don't get the floating answer in 2nd case. Any suggestion ?
The reason is that compiler can not deduce the type for T.
How it should understand what T is for your last example? The type of the first argument (b) is double[], while it is T[] in the function definition. Therefore it looks like that T should be double. However, the type of the third argument (40) is int, so it looks like T should be int. Hence the error.
Changing 40 to 40.0 makes it work. Another approach is to use two different types in template declaration:
#include <iostream>
using namespace std;
template <class T, class S = T>
T sumArray( T arr[], int size, S s =0)
{
int i;
T res = s;
for(i=0;i<size;i++)
{ res += arr[i];
}
return res;
}
int main()
{
int a[] = {1,2,3};
double b[] = {1.0,2.0,3.1};
cout << sumArray(a,3) << endl;
cout << sumArray(b,3) << endl;
cout << sumArray(a,3,10) << endl;
cout << sumArray(b,3,40) << endl; //uncommenting this line gives error
return 0;
}
Note that I had to cast s to T explicitly, otherwise the last example will lose fractional part.
However, this solution will still not work for sumArray(a,3,10.1) because it will cast 10.1 to int, so if this is also a possible use case, a more accurate treatment is required. A fully working example using c++11 features might be like
template <class T, class S = T>
auto sumArray(T arr[], int size, S s=0) -> decltype(s+arr[0])
{
int i;
decltype(s+arr[0]) res = s;
...
Another possible improvement for this template function is auto-deduction of array size, see TartanLlama's answer.
sumArray(b,3,40)
The type of 40 is int, but the type of b is double[3]. When you pass these in as arguments, the compiler gets conflicting types for T.
A simple way to fix this is to just pass in a double:
sumArray(b,3,40.0)
However, you would probably be better off allowing conversions at the call site by adding another template parameter. You can also add one to deduce the size of the array for you so that you don't need to pass it explicitly:
template <class T, class U=T, std::size_t size>
U sumArray(T (&arr) [size], U s = 0)
The U parameter is defaulted to T to support the default value for s. Note that to deduce the size of the array, we need to pass a reference to it rather than passing by value, which would result in it decaying to a pointer.
Calling now looks like this:
sumArray(b,40)
Live Demo
In
template <class T>
T sumArray( T arr[], int size, T s =0)
^ ^
Both (deducible) T should match.
In sumArray(b, 3, 40), it is double for the first one, and int for the second one.
There is several possibilities to fix problem
at the call site, call sumArray(b, 3, 40.0) or sumArray<double>(b, 3, 40);
Use extra parameter:
template <typename T, typename S>
auto sumArray(T arr[], int size, S s = 0)
Return type may be T, S, or decltype(arr[0] + s) depending of your needs.
make a parameter non deducible:
template <typename T> struct identity { using type = T;};
// or template<typename T> using identity = std::enable_if<true, T>;
template <typename T>
T sumArray(T arr[], int size, typename identity<T>::type s = 0)
Should be
sumArray(b,3,40.0)
so, T will be deduced to double. In your code it's int.
Another option when deduction fails is to explicitly tell the compiler what you mean:
cout << sumArray<double>(b,3,40) << endl;
The compiler does not know whether T should be int or double.
You might want to do the following, in order to preserve the highest precision of the types passed:
template <class T, class S>
std::common_type_t <T, S> sumArray (T arr [], std::size_t size, S s = 0)
{
std::common_type_t <T, S> sum = s;
for (std::size_t i = 0; i != size; ++i)
{
sum += arr[i];
}
return sum;
}
The function you are writing, however, already exists. It's std::accumulate:
std::cout << std::accumulate (std::begin (b), std::end (b), 0.0) << std::endl;
Templates only accept one type of data, for example if you send an array of double, then the runtime will deduce :
Template = double[]
so every time he will see it he will expect an array of doubles.
sumArray(b,3,40) passes "b" (which is an array of doubles) but then you pass "40" which the runtime cannot implicitly convert to double.
So the code
sumArray(b,3,40.0)
will work

C++: template to check if expression compiles

When writing template specialization with SFINAE you often come to the point where you need to write a whole new specialization because of one small not-existing member or function. I would like to pack this selection into a small statement like orElse<T a,T b>.
small example:
template<typename T> int get(T& v){
return orElse<v.get(),0>();
}
is this possible?
The intent of orElse<v.get(),0>() is clear enough, but if such a thing could exist,
it would have to be be one of:
Invocation Lineup
orElse(v,&V::get,0)
orElse<V,&V::get>(v,0)
orElse<V,&V::get,0>(v)
where v is of type V, and the function template thus instantiated
would be respectively:
Function Template Lineup
template<typename T>
int orElse(T & obj, int(T::pmf*)(), int deflt);
template<typename T, int(T::*)()>
int orElse(T & obj, int deflt);
template<typename T, int(T::*)(), int Default>
int orElse(T & obj);
As you appreciate, no such a thing can exist with the effect that you want.
For any anyone who doesn't get that,
the reason is simply this: None of the function invocations in the Invocation Lineup
will compile if there is no such member as V::get. There's no getting round
that, and the fact that the function invoked might be an instantiation of a
function template in the Function Template Lineup makes no difference whatever.
If V::get does not exist, then any code that mentions it will not compile.
However, you seem to have a practical goal that need not be approached
in just this hopeless way. It looks as if, for a given name foo and an given type R,
you want to be able to write just one function template:
template<typename T, typename ...Args>
R foo(T && obj, Args &&... args);
which will return the value of R(T::foo), called upon obj with arguments args...,
if such a member function exists, and otherwise return some default R.
If that's right, it can be achieved as per the following illustration:
#include <utility>
#include <type_traits>
namespace detail {
template<typename T>
T default_ctor()
{
return T();
}
// SFINAE `R(T::get)` exists
template<typename T, typename R, R(Default)(), typename ...Args>
auto get_or_default(
T && obj,
Args &&... args) ->
std::enable_if_t<
std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
>::value,R>
{
return obj.get(std::forward<Args>(args)...);
}
// SFINAE `R(T::get)` does not exist
template<typename T, typename R, R(Default)(), typename ...Args>
R get_or_default(...)
{
return Default();
}
} //namespace detail
// This is your universal `int get(T,Args...)`
template<typename T, typename ...Args>
int get(T && obj, Args &&... args)
{
return detail::get_or_default<T&,int,detail::default_ctor>
(obj,std::forward<Args>(args)...);
}
// C++14, trivially adaptable for C++11
which can be tried out with:
#include <iostream>
using namespace std;
struct A
{
A(){};
int get() {
return 1;
}
int get(int i) const {
return i + i;
}
};
struct B
{
double get() {
return 2.2;
}
double get(double d) {
return d * d;
}
};
struct C{};
int main()
{
A const aconst;
A a;
B b;
C c;
cout << get(aconst) << endl; // expect 0
cout << get(a) << endl; // expect 1
cout << get(b) << endl; // expect 0
cout << get(c) << endl; // expect 0
cout << get(a,1) << endl; // expect 2
cout << get(b,2,2) << endl; // expect 0
cout << get(c,3) << endl; // expect 0
cout << get(A(),2) << endl; // expect 4
cout << get(B(),2,2) << endl; // expect 0
cout << get(C(),3) << endl; // expect 0
return 0;
}
There is "compound SFINAE" in play in the complicated return type:
std::enable_if_t<
std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
>::value,R>
If T::get does not exist then decltype(obj.get(std::forward<Args>(args)...)
does not compile. But if it does compile, and the return-type of T::get is
something other than R, then the std::enable_if_t type specifier does not
compile. Only if the member function exists and has the desired return type R
can the R(T::get) exists case be instantiated. Otherwise the
catch-all R(T::get) does not exist case is chosen.
Notice that get(aconst) returns 0 and not 1. That's as it should be,
because the non-const overload A::get() cannot be called on a const A.
You can use the same pattern for any other R foo(V & v,Args...) and
existent or non-existent R(V::foo)(Args...).
If R is not default-constructible, or if you want the default R that
is returned when R(V::foo) does not exist to be something different from
R(), then define a function detail::fallback (or whatever) that returns the
desired default R and specify it instead of detail::default_ctor
How nice it would be it you could further template-paramaterize the pattern
to accomodate any possible member function of T with any possible return
type R. But the additional template parameter you would need for that would
be R(T::*)(typename...),and its instantiating value would have to be
&V::get (or whatever), and then the pattern would
force you into the fatal snare of mentioning the thing whose existence is in doubt.
Yes, this is more or less possible. It is known as a "member detector". See this wikibooks link for how to accomplish this with macros. The actual implementation will depend on whether you are using pre- or post-C++11 and which compiler you are using.

Copy two tuples with different sizes

I am experimenting with some tuples, and I find myself in the weird position of asking this: how can I copy two tuples that differ in their sizes? Of course, this is intended limited to the minimum length of the two tuples.
So, for instance, let's create three tuples:
std::tuple<int, char, float> a(-1, 'A', 3.14);
std::tuple<int, char, double> b = a;
std::tuple<long, int, double, char> c;
Now, a and b differ in types, and the assignment work (obviously). As for a and c the things get a little more confusing.
My first implementation failed, since I don't know how to recurse on variadic templates with a specific type, so something like this won't work:
template <class T, class U>
void cp(std::tuple<T> from, std::tuple<U> to)
{
}
template <class T, class... ArgsFrom, class U, class... ArgsTo>
void cp(std::tuple<T, ArgsFrom...> from, std::tuple<U, ArgsTo...> to)
{
std::get<0>(to) = std::get<0>(from);
// And how to generate the rest of the tuples?
}
That function won't do anything. So I've devised a second failing attempt, using not the types, but the sizes:
template<class From, class To, std::size_t i>
void copy_tuple_implementation(From &from, To &to)
{
std::get<i>(to) = std::get<i>(from);
copy_tuple_implementation<From, To, i - 1>(from, to);
}
template<>
void copy_tuple_implementation<class From, class To, 0>(From &from, To &to)
{
}
template<class From, class To>
void copy_tuple(From &from, To &to)
{
constexpr std::size_t from_len = std::tuple_size<From>::value;
constexpr std::size_t to_len = std::tuple_size<To>::value;
copy_tuple_implementation<From, To, from_len < to_len ? from_len - 1 : to_len - 1>(from, to);
}
But that won't compile. I have too many errors to display here, but the most significant ones are:
Static_assert failed "tuple_element index out of range"
No type named 'type' in 'std::__1::tuple_element<18446744073709551612, std::__1::__tuple_types<> >'
Read-only variable is not assignable
No viable conversion from 'const base' (aka 'const __tuple_impl<typename __make_tuple_indices<sizeof...(_Tp)>::type, int, int, double>') to 'const __tuple_leaf<18446744073709551615UL, type>'
The interesting part is the index out of range, and the fact that I cannot copy an element with std::get<>.
Can anyone help me in this?
Thanks!
Here's one possibility, using C++14's ready-made integer sequence template (but this is easily reproduced manually if your library doesn't include it):
#include <tuple>
#include <utility>
template <std::size_t ...I, typename T1, typename T2>
void copy_tuple_impl(T1 const & from, T2 & to, std::index_sequence<I...>)
{
int dummy[] = { (std::get<I>(to) = std::get<I>(from), 0)... };
static_cast<void>(dummy);
}
template <typename T1, typename T2>
void copy_tuple(T1 const & from, T2 & to)
{
copy_tuple_impl(
from, to,
std::make_index_sequence<std::tuple_size<T1>::value>());
}
Example:
#include <iostream>
int main()
{
std::tuple<int, char> from { 1, 'x' };
std::tuple<int, char, bool> to;
copy_tuple(from, to);
std::cout << "to<0> = " << std::get<0>(to) << "\n";
}
Another option is to use operator overloading to simulate partial-specialization of your function:
template <std::size_t N>
struct size_t_t {};
template<class From, class To, std::size_t i>
void copy_tuple_implementation(From &from, To &to, size_t_t<i>)
{
std::get<i>(to) = std::get<i>(from);
copy_tuple_implementation(from, to, size_t_t<i-1>{});
}
template<class From, class To>
void copy_tuple_implementation(From &from, To &to, size_t_t<0>)
{
std::get<0>(to) = std::get<0>(from);
}
Or you could just use a helper class:
template<class From, class To, std::size_t i>
struct CopyTuple
{
static void run(From &from, To &to)
{
std::get<i>(to) = std::get<i>(from);
CopyTuple<From,To,i-1>::run(from, to);
}
};
template<class From, class To>
struct CopyTuple<From,To,0>
{
static void run(From &from, To &to)
{
std::get<0>(to) = std::get<0>(from);
}
};
The goal here is to get a clean syntax at point of use.
I define auto_slice which takes a tuple, and auto slices it for the expression.
The intended use is
auto_slice(lhs)=auto_slice(rhs);
and it just works.
// a helper that is a slightly more conservative `std::decay_t`:
template<class T>
using cleanup_t = std::remove_cv_t< std::remove_reference_t< T > >;
// the workhorse. It holds a tuple and in an rvalue context
// allows partial assignment from and to:
template<class T,size_t s0=std::tuple_size<cleanup_t<T>>{}>
struct tuple_slicer{
T&&t;
// Instead of working directly within operators, the operators
// call .get() and .assign() to do their work:
template<class Dest,size_t s1=std::tuple_size<Dest>{}>
Dest get() && {
// get a pack of indexes, and use it:
using indexes=std::make_index_sequence<(s0<s1)?s0:s1>;
return std::move(*this).template get<Dest>(indexes{});
}
template<class Dest,size_t s1=std::tuple_size<Dest>{},size_t...is>
Dest get(std::index_sequence<is...>) && {
// We cannot construct a larger tuple from a smaller one
// as we do not know what to populate the remainder with.
// We could default construct them, I guess?
static_assert(s0>=s1,"use auto_slice on target");
using std::get;
return Dest{ get<is>(std::forward<T>(t))... };
}
// allows implicit conversion from the slicer:
template<class Dest>
operator Dest()&&{
return std::move(*this).template get<Dest>();
}
// now we are doing the assignment work. This function
// does the pack expansion hack, excuse the strangeness of the
// code in it:
template<class Src, size_t...is>
void assign(std::index_sequence<is...>,tuple_slicer<Src>&&rhs)&&{
using std::get;
int _[]={0,(void(
get<is>(std::forward<T>(t))=get<is>(std::forward<Src>(rhs.t))
),0)...};
(void)_; // remove warnings
}
// assign from another slicer:
template<class Src,size_t s1>
void operator=(tuple_slicer<Src,s1>&&rhs)&&{
using indexes=std::make_index_sequence<(s0<s1)?s0:s1>;
std::move(*this).assign(indexes{},std::move(rhs));
}
// assign from a tuple. Here we pack it up in a slicer, and use the above:
template<class Src>
void operator=(Src&& src)&&{
std::move(*this) = tuple_slicer<Src>{ std::forward<Src>(src) };
}
};
// this deduces the type of tuple_slicer<?> we need for us:
template<class Tuple>
tuple_slicer<Tuple> auto_slice(Tuple&&t){
return {std::forward<Tuple>(t)};
}
The slice is only required on whichever side is smaller, but can be done on both sides (for generic code) if required.
It also works at construction. On the right hand side, it should work with std::arrays and pairs and tuples. On the left hand side, it may not work with arrays, due to requirement to construct with {{}}.
live example
Here is the recursive solution your were originally trying to figure out:
#include <tuple>
// Limit case
template<std::size_t I = 0, typename ...From, typename ...To>
typename std::enable_if<(I >= sizeof...(From) || I >= sizeof...(To))>::type
copy_tuple(std::tuple<From...> const & from, std::tuple<To...> & to) {}
// Recursive case
template<std::size_t I = 0, typename ...From, typename ...To>
typename std::enable_if<(I < sizeof...(From) && I < sizeof...(To))>::type
copy_tuple(std::tuple<From...> const & from, std::tuple<To...> & to)
{
std::get<I>(to) = std::get<I>(from);
copy_tuple<I + 1>(from,to);
}
You do not need std::index_sequence or similar apparatus, and this
solution has two strengths that your accepted one does not:
It will compile, and do the right thing, when from is longer than to: the
excess trailing elements of from are ignored.
It will compile, and do the right thing, when either from or to is an
empty tuple: the operation is a no-op.
Prepend it to this example:
#include <iostream>
int main()
{
std::tuple<int, char> a { 1, 'x' };
std::tuple<int, char, bool> b;
// Copy shorter to longer
copy_tuple(a, b);
std::cout << "b<0> = " << std::get<0>(b) << "\n";
std::cout << "b<1> = " << std::get<1>(b) << "\n";
std::cout << "b<2> = " << std::get<2>(b) << "\n\n";
// Copy longer to shorter
std::get<0>(b) = 2;
std::get<1>(b) = 'y';
copy_tuple(b,a);
std::cout << "a<0> = " << std::get<0>(a) << "\n";
std::cout << "a<1> = " << std::get<1>(a) << "\n\n";
// Copy empty to non-empty
std::tuple<> empty;
copy_tuple(empty,a);
std::cout << "a<0> = " << std::get<0>(a) << "\n";
std::cout << "a<1> = " << std::get<1>(a) << "\n\n";
// Copy non-empty to empty
copy_tuple(a,empty);
return 0;
}
(g++ 4.9/clang 3.5, -std=c++11)