Reputation: 349
I would like to write a class that takes a size N
(> 0) and a variable number of arguments (>= N). It should have a constructor that takes N arguments and a member std::tuple
which has the same type:
template <size_t N, typename... Args>
struct Example {
// A constructor taking N parameters of type Args[N], initializing the member tuple
// (e.g. param 1 has type Args[0], param 2 has type Args[1], ...,
// param N has type Args[N-1])
// A tuple with N elements, each corresponding to Args[N]
// (e.g. std::tuple<Args[0], ..., Args[N-1]>)
//For instance Example<3, int, float, int, bool> should result in
constexpr Example(int a, float b, int c): t(a, b, c) {}
std::tuple<int, float, int> t;
}
In general: Is this possible? If not are there viable alternatives? Why does/ doesn't this work? I'm using C++20.
Upvotes: 2
Views: 357
Reputation: 16765
There are so-called Template Deduction Guides. It is marked in code below with comment "// Template Deduction Guide".
This guide shows how to deduce template types of a structure from arguments of constructor.
Now you can call constructor (as I did in main()) with any types.
#include <tuple>
template <std::size_t N, typename ... Args>
struct Example {
constexpr Example(Args && ... args)
: t(std::forward<Args>(args)...) {}
std::tuple<Args...> t;
};
// Template Deduction Guide
template <typename ... Args>
Example(Args && ...) -> Example<sizeof...(Args), Args...>;
#include <iostream>
#include <iomanip>
int main() {
Example example(int{3}, float{5.7}, bool{true});
std::cout
<< std::boolalpha << "size "
<< std::tuple_size_v<decltype(example.t)> << " tuple "
<< std::get<0>(example.t) << " " << std::get<1>(example.t)
<< " " << std::get<2>(example.t) << std::endl;
}
Output:
size 3 tuple 3 5.7 true
Upvotes: -1
Reputation: 4735
Here is an implementation using C++20:
#include <cstddef>
#include <utility>
#include <tuple>
template <class... Ts> struct typelist;
template <size_t N, class, class> struct take;
// Takes N elements of the given type pack
template <size_t N, class... Ts>
using take_t = typename take<N, typelist<>, typelist<Ts...>>::type;
template <class Take, class Drop>
struct take<0, Take, Drop> {
using type = Take;
};
template <size_t N, class T, class... Ts, class... Us>
requires(N > 0)
struct take<N, typelist<Us...>, typelist<T, Ts...>> {
using type = typename take<N - 1, typelist<Us..., T>, typelist<Ts...>>::type;
};
template <class Ts>
struct make_ctor;
template <class... Ts>
struct make_ctor<typelist<Ts...>> {
constexpr make_ctor(Ts... ts) : tuple(ts...) {}
std::tuple<Ts...> tuple;
};
template <size_t N, class... Args>
struct Example :
make_ctor<take_t<N, Args...>> {
using make_ctor<take_t<N, Args...>>::make_ctor;
};
int main() {
Example<3, int, float, int, bool> e(3, 3.14, 4);
}
What we do here is, first we drop the extra template arguments using the take
meta-function. However, since type packs are not first class in C++, we only get to take a typelist
of our desired types. To create a tuple + a constructor from a given typelist
, we have a helper type called make_ctor
. By inheriting its constructor, we get the desired API.
For production, see Barry's answer and use an existing library for the metaprogramming part.
Upvotes: 2
Reputation: 66230
Using std::tuple_element
and an helper base class, seems to me that you can write something as follows (C++14 is enough)
#include <tuple>
#include <string>
template <typename ...>
struct ExHelper;
template <std::size_t ... Is, typename Tpl>
struct ExHelper<std::index_sequence<Is...>, Tpl>
{
using t_type = std::tuple<typename std::tuple_element<Is, Tpl>::type...>;
t_type t;
constexpr ExHelper(typename std::tuple_element<Is, Tpl>::type ... args)
: t{std::move(args)...}
{ }
};
template <std::size_t N, typename... Args>
struct Example : public ExHelper<std::make_index_sequence<N>,
std::tuple<Args...>> {
static_assert( N <= sizeof...(Args), "!" );
using ExHelper<std::make_index_sequence<N>,
std::tuple<Args...>>::ExHelper;
};
int main ()
{
Example<3, int, float, int, std::string> e0{1, 2.0f, 3};
static_assert( std::is_same<decltype(e0)::t_type,
std::tuple<int, float, int>>::value, "!" );
}
Upvotes: 1
Reputation: 304122
To the extent I understand the question, it simply seems to be asking how to produce a tuple
from arguments. Which, using Boost.Mp11, is a short one-liner (as always):
template <size_t N, typename... Args>
using Example = mp_take_c<N, std::tuple<Args...>>;
Rather than Example<3, int, float, int, bool>
being some type that has a member tuple<int, float, int>
with one constructor, it actually is tuple<int, float int>
.
If you, for some reason, specifically need exactly a member tuple and exactly the constructor specified, we can do easily enough:
template <typename... Ts>
struct ExampleImpl {
std::tuple<Ts...> t;
constexpr ExampleImpl(Ts... ts) : t(ts...) { }
};
template <size_t N, typename... Args>
using Example = mp_take_c<N, ExampleImpl<Args...>>;
Upvotes: 3
Reputation: 118077
You could make_index_sequence<N>
to help with extracting the first N
from Args...
.
Example:
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>
// Create a tuple from the sizeof...(I) first elements in the supplied tuple:
template <class... Ts, size_t... I>
constexpr auto extract_from_tuple(std::tuple<Ts...>&& tmp, std::index_sequence<I...>) {
static_assert(sizeof...(Ts) >= sizeof...(I));
// make a new tuple:
return std::make_tuple(std::move(std::get<I>(tmp))...);
}
template <size_t N, class... Args>
struct Example {
template<class... Ts, std::enable_if_t<sizeof...(Ts)==N, int> = 0>
constexpr Example(Ts&&... args)
: t{extract_from_tuple(std::make_tuple(std::forward<Ts>(args)...),
std::make_index_sequence<N>{})} {}
// using the same extract_from_tuple function to deduce the type of `t`
decltype(extract_from_tuple(std::make_tuple(std::declval<Args>()...),
std::make_index_sequence<N>{})) t;
};
int main() {
Example<3, int, float, double, bool> foo(1, 2.f, 3.);
static_assert(std::is_same_v<decltype(foo.t), std::tuple<int, float, double>>);
}
Upvotes: 0