简体   繁体   中英

Initialize array inside variadic template class

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

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.

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM