Reputation: 2444
I cannot make my head around variadic tempates. I want to do very simple thing
Tuple t{1,2,3};
should crate tuple of size 3 containing array {1,2,3} ( t.data = {1,2,3}
)
That means it should do 2 things:
Tuple<T,3>
size 3 ( Tuple<>::data[3]
)Tuple<>::data
with the numbers form std::initializer_listThis does not work:
template<typename T, T...args>
struct Tuple{
T data[sizeof...(args)];
Tuple(const T& args...):data{args...}{};
};
I tried all sorts of variations like:
template<typename T, T...args>
//template<typename T, Args...args>
struct Tuple{
T data[sizeof...(args)];
//T data{args...};
//template <typename ...Args>
//Tuple(T... args):data{args...}{};
Tuple(const T& args...):data{args...}{};
//Tuple(T* ...args):data{args...}{};
};
perhaps I don't get difference between
T...args
and typename ...Args
and args...
I'm trying to use this as simple example to understand variadic templates and avoid using std::initializer_list
Upvotes: 1
Views: 3841
Reputation: 101
based on this good explanation.
#1 create an array of nth generated integers
template <typename Container, int... I>
Container iota_impl(std::integer_sequence<int, I...>) {
return {I...};
}
template <typename T, size_t N>
auto iota_array() {
using Sequence = std::make_integer_sequence<int, N>;
return iota_impl<std::array<T, N>>(Sequence{});
}
...
auto arr1 = iota_array<int, 10>();
will create std::array<int, 10>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
#2 create an array of integer passed via initializer_list
template <typename T, T...I>
auto iota_array2() {
constexpr auto N = sizeof...(I);
return std::array<T, N>({I...});
}
...
auto arr2 = iota_array2<int, 3,2,7,4,5,6>();
will create std::array<int, 6>{3,2,7,4,5,6}
PS if it should be wrapped in Tuple, it can be.
PPS c++17
Upvotes: 0
Reputation: 66200
I cannot make my head around variadic tempates. I want to do very simple thing
Tuple t{1,2,3};
should crate tuple of size 3 containing
array {1,2,3}
(t.data = {1,2,3}
)
Not sure but, if I understand correctly, your trying to re-create std::array
.
What you want it's impossible before C++17 because your Tuple
it's a template class, so before C++17 you needs to explicit the template arguments.
Starting from C++17, you can use deduction guides.
What you want (again: if I understand correctly) is almost the std::array
deduction guide
template <class T, class... U> array(T, U...) -> array<T, 1 + sizeof...(U)>;
In you case become
#include <type_traits>
template <typename T, std::size_t N>
struct Tuple
{
T data[N];
};
template <typename T, typename ... U>
Tuple(T, U...) -> Tuple<T, 1 + sizeof...(U)>;
int main ()
{
Tuple t{1, 2, 3};
static_assert( std::is_same_v<decltype(t), Tuple<int, 3u>> );
}
Observe that a constructor isn't strictly required because the argument are used to initialize the member (the C-style array).
This deduction guide
template <typename T, typename ... U>
Tuple(T, U...) -> Tuple<T, 1 + sizeof...(U)>;
deduce the type of the Tuple::data
array from the first argument and the other argument are used only to deduce the size of the array; this can be a problem if the types of the arguments are different; by example
Tuple t1{1l, 2, 3}; // become Tuple<long, 3u>
Tuple t2{2, 2l, 3}; // become Tuple<int, 3u>
Take also in count that, for std::array
The program is ill-formed if
(std::is_same_v<T, U> && ...)
is nottrue
To solve this problem and have something more flexible, you can use std::common_type_t
, as suggested in other answers, so deduction guide become
template <typename ... Ts>
Tuple(Ts...) -> Tuple<std::common_type_t<Ts...>, sizeof...(Ts)>;
and both cases become Tuple<long, 3u>
Tuple t1{1l, 2, 3}; // become again Tuple<long, 3u>
Tuple t2{2, 2l, 3}; // now become Tuple<long, 3u>
Perhaps I don't get difference between T...args and typename ...Args and args...
Look for a good C++ book but, making it simple
(1) typename ... Args
declare a template variadic sequence of types, for a class/struct, for a using
declaration, for a deduction guide, for a function.
So
template <typename ... Args>
struct foo
{ };
define a template struct that receive zero or more template types arguments and you can declare a variable as follows
foo<short, int, long, long long> f;
(2) T ... args
declare a variadic template list not of types but of elements of type T
What is T
? Another template parameter.
So, by example, one of your Tuple
version in your question
template struct Tuple { /* ... */ };
and a variable should be declared as follows
Tuple<int, 1, 2, 3> t{1, 2, 3}
that is very redundant in your case.
(3) args...
(with ellipsis after the name) is the use a variadic list (of types or values)
By example
template <typename ... Args>
void foo (Args ... args)
{ bar(args...); }
declare and define a variadic template foo()
function with a template variadic list of types
template <typename ... Args> // <--- declare a variadic list of types Args
and at every type correspond a value, so you declare also a variadic list args
of values
void foo (Args ... args) // <--- declare a variadic list of args values of types Args
and the statement expand the pack of values args
and pass they to another function
bar(args...); // <--- expand the args pack and pass the value to bar.
Upvotes: 2
Reputation: 217275
Alternative using std::index_sequence
:
template <typename T, std::size_t> using always_t = T;
template <typename T, typename Seq> struct Tuple;
template <typename T, std::size_t...Is>
struct Tuple<T, std::index_sequence<Is...>>{
T data[sizeof...(Is)];
Tuple(const always_t<T, Is>&... args) : data{args...}{}
};
// Deduction guide (C++17)
template <typename ... Ts>
Tuple(const Ts&...) -> Tuple<std::common_type_t<Ts...>, std::index_sequence_for<Ts...>>;
Tuple a{1,2,3,4,5};
Upvotes: 1
Reputation: 118340
This is surprisingly difficult. The only way I could think of making this work is to have the array size as a template parameter, instead of somehow deducing this from the actual constructor parameters, and to use C++17 deduction guides.
Tested with gcc 9.1, with -std=c++17
:
#include <cstdlib>
#include <iostream>
#include <type_traits>
#include <utility>
template<typename T, size_t n>
struct Tuple{
T data[n];
template<typename ...Args>
Tuple(Args && ...args):data{std::forward<Args>(args)...}{};
};
template<typename ...Args>
Tuple(Args && ...args)
-> Tuple<std::common_type_t<std::remove_reference_t<Args>...>,
sizeof...(args)>;
Tuple a{1,2,3,4,5};
int main()
{
std::cout << std::is_same_v<decltype(a),
Tuple<int, 5>> << std::endl;
std::cout << a.data[2] << std::endl;
}
Upvotes: 0