Reputation: 60361
Consider the following class:
// Class definition
template <template <class...> class... Templates>
class template_pack
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept;
};
// Class template argument deduction guide
template <class... Types>
template_pack(const Types&...) -> template_pack</* something here */>
We suppose that the Types...
are of the form template <class...> class... Templates
. What I would like is:
template_pack pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
to lead to:
template_pack<std::vector, std::list, std::deque>;
How to make that work?
Upvotes: 3
Views: 4967
Reputation: 217105
I "succeed" with additional traits:
template <typename T> struct template_traits;
// Variadic case
template <template <class...> class C, typename ... Ts>
struct template_traits<C<Ts...>>
{
template <typename ... Us>
using template_type = C<Us...>;
};
And then, argument deduction is:
// Class template argument deduction guide
template <class... Types>
template_pack(Types&&...)
-> template_pack<template_traits<std::decay_t<Types>>::template template_type...>;
Issue is that aliases are not really identical (gcc considers some aliases as identical contrary to clang) (I opened a question for that BTW)
template_traits<std::vector>::template_type
is not std::vector
even if for any T
, A
, template_traits<std::vector>::template_type<T, A>
is not std::vector<T, A>
.
Upvotes: 2
Reputation: 66200
How to make that work?
I don't see a way: there is ever something that can't be deduced.
Not exactly what you asked but the best I can imagine pass trough a custom type-traits ttw
(for "template-template-wrapper")
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
that, using implicit deduction guides, extracts the template-template from the type received from constructor and use it as template parameter.
So you can write template_pack
with a constructor that receives ttw<Templates>
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
that you can use as follows (again: trough implicit deduction guides)
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
The problem is that it's necessary explicitly wrap the arguments in ttw{}
because, to make an example, a std::vector<int>
is convertible to a ttw<std::vector>
but isn't a ttw<std::vector>
. So, passing std::vector{}
instead of ttw{std::vector{}}
, we have the usual chicken/egg problem of a type that can't be deduced because, to deduce it, is needed a conversion that require to knowledge of type that we want deduce.
Obviously, you can demand the explicit ttw
wrapping works to a specific make_template_pack()
function
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
The following is a full compiling example
#include <map>
#include <set>
#include <vector>
#include <type_traits>
template <template <typename...> class C>
struct ttw
{
template <typename ... Ts>
constexpr ttw (C<Ts...> const &)
{ }
};
template <template <typename...> class... Templates>
struct template_pack
{
constexpr template_pack (ttw<Templates> const & ...)
{ }
};
template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
{ return template_pack{ttw{std::forward<Ts>(ts)}...}; }
int main ()
{
template_pack tp1 {ttw{std::vector<int>{}},
ttw{std::set<long>{}},
ttw{std::map<char, short>{}}};
auto tp2 { make_template_pack(std::vector<long>{},
std::set<int>{},
std::map<char, short>{}) };
using t0 = template_pack<std::vector, std::set, std::map>;
using t1 = decltype(tp1);
using t2 = decltype(tp2);
static_assert( std::is_same<t0, t1>::value );
static_assert( std::is_same<t0, t2>::value );
}
Upvotes: 4
Reputation: 61910
There's a shortcut you can take if each template has only one argument:
template <template<class> class... Templates, class... Types>
template_pack(const Templates<Types>&...) -> template_pack<Templates...>;
With only one argument each, it's easy to split up one pack amongst all the templates.
Unfortunately, I don't know of any way to have a separate pack per template without knowing the number of templates in advance. Therefore, a layer of indirection through a helper seems required. In addition, deduction guides must be of the form -> template_pack<something>
, presumably to avoid having the compiler do too much work or run into impossible problems. Given this, the class needs a slight tweak:
template <template <class...> class... Templates>
class holder {};
// Class definition
template<class Holder>
class template_pack;
template <template <class...> class... Templates>
class template_pack<holder<Templates...>>
{
public:
template <class... Types>
constexpr template_pack(const Types&...) noexcept {}
};
With this tweak, we can make a helper (that could probably be simplified to be a bit more straightforward):
template<template<class...> class... TTs>
struct result {
using type = holder<TTs...>;
};
template<class T>
struct type {};
template<class Prev, class Current, class... Rest>
auto helper() {
return []<template<class...> class... PrevTTs, template<class...> class CurrTT, class... CurrTs>(result<PrevTTs...>, type<CurrTT<CurrTs...>>) {
if constexpr (sizeof...(Rest) == 0) {
return result<PrevTTs..., CurrTT>{};
} else {
return helper<result<PrevTTs..., CurrTT>, Rest...>();
}
}(Prev{}, type<Current>{});
}
I use C++20's templated lambdas to pull apart two templates from their arg packs inline instead of having an additional helper layer, but that additional layer is still possible in earlier standards, just uglier. The helper recursively takes a previous result, pulls apart one template at a time, adds it to the result, and recursively calls itself until there are no arguments left.
With this helper, it becomes possible to make the deduction guide:
// Class template argument deduction guide
template <typename... Ts>
template_pack(const Ts&...) -> template_pack<typename decltype(helper<result<>, Ts...>())::type>;
You can find a full example here. It might also be possible to improve this code somewhat significantly, but the core idea is there.
Upvotes: 1
Reputation: 425
Something like that seems to be working
#include <iostream>
#include <vector>
#include <list>
#include <deque>
template<typename... TS>
struct Pack;
template<typename S, typename... TS>
struct Pack<S, TS...> {
S s;
Pack<TS...> ts;
static constexpr size_t size = Pack<TS...>::size + 1;
constexpr Pack(S&& s, TS&&... ts) noexcept
: s(s)
, ts(std::forward<TS>(ts)...)
{}
};
template<typename S>
struct Pack<S> {
S s;
static constexpr size_t size = 1;
constexpr Pack(S&& s) noexcept
: s(s)
{}
};
template<>
struct Pack<> {
static constexpr size_t size = 0;
};
template<typename... TS>
constexpr auto make_pack(TS&&... ts) noexcept {
return Pack<TS...>(std::forward<TS>(ts)...);
}
int main() {
auto empty_pack = make_pack();
std::cout << empty_pack.size << std::endl; // 0
auto vector_pack = make_pack(std::vector<int>{});
std::cout << vector_pack.size << std::endl; // 1
auto vector_list_deque_pack = make_pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
std::cout << vector_list_deque_pack.size << std::endl; // 3
}
Upvotes: 0