1

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:

  • Create Tuple<T,3> size 3 ( Tuple<>::data[3] )
  • fill Tuple<>::data with the numbers form std::initializer_list

This 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

4 Answers 4

2

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 not true

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.
Sign up to request clarification or add additional context in comments.

Comments

1

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};

Demo

Comments

0

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;
}

3 Comments

Thanks, I'm starting to get what std::common_type_t<std::remove_reference_t<Args>...> does. Can be this template<typename ...Args> Tuple(Args && ...args) -> made more general to work for any similar template class constructor, not just Tuple ?
still does not work expect constructor,destructor or type conversion before ; ... so I tried to add it like -> Tuple<std::common_type_t<std::remove_reference_t<Args>...>,sizeof...(args)>::Tuple(Args && ...args); but that expects type specifier
No, you have to use a deduction guide, in the manner I outlined, and a compiler that supports C++17. I do not believe that template parameters can be deduced directly from a constructor call, in this manner.
0

based on this good explanation.

#0 generate an array

template <int... Vals>
constexpr auto gen_array() {
    constexpr size_t N = sizeof...{Vals};
    return std::array<int, N>{Vals...};
}

....
// will get std::array with (1, 2, 3, 4, 5)
constexpr auto arr = gen_array<1, 2, 3, 4, 5>();

#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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.