I need some code to check whether a certain template is part of a parameter pack. To implement the check for normal classes, I use the multiple-inheritance based approach outlined e.g. by Louis Dionne here or Agustín Bergé here.
The idea is that you wrap every class T
in the pack in an PackEntry
class, and then have PackIndex
inherit from all PackEntry
classes. This way, if you are looking for a class A
, all you need to do is check if a PackIndex
can be converted to the correct PackEntry
. Throwing everything together, it looks like this:
#include <cstddef>
#include <utility>
template <class T>
struct PackEntry
{
using type = T;
};
template <class... Ts>
struct PackIndex : PackEntry<Ts>...
{};
template <class T, class... Ts>
struct PackSearcher
{
static constexpr std::false_type check(...);
// This overload is used if the PackIndex below
// derives from PackEntry<T>, the overload above
// otherwise.
static constexpr std::true_type check(PackEntry<T>);
using type =
decltype(check(PackIndex<Ts...>{}));
static constexpr bool
value()
{
return type{};
}
};
template <class... Ts>
struct Pack
{
template<class T>
static constexpr bool has() {
return PackSearcher<T, Ts...>::value();
}
};
int main() {
static_assert(Pack<int, void>::has<int>());
static_assert(! Pack<int, void>::has<bool>());
}
Now, this is all neat, but say that I have a template Tmpl
, and I want to know whether my pack contains any specialization of that template. I's easy to do if the template has a specific form, say template <std::size_t> class Tmpl {};
Basically, the only difference is that inside the PackSearcher
(which I now call TmplPackSearcher
, you make the compiler derive the correct N
template parameter for the template.
#include <cstddef>
#include <utility>
template <class T>
struct PackEntry
{
using type = T;
};
template <class... Ts>
struct PackIndex : PackEntry<Ts>...
{
};
template <template <std::size_t> class T, class... Ts>
struct TmplPackSearcher
{
static constexpr std::false_type check(...);
template <std::size_t N>
static constexpr std::true_type check(PackEntry<T<N>>);
using type =
decltype(check(PackIndex<Ts...>{}));
static constexpr bool
value()
{
return type{};
}
};
template <class... Ts>
struct Pack
{
template<template <std::size_t> class T>
static constexpr bool has_tmpl() {
return TmplPackSearcher<T, Ts...>::value();
}
};
template<std::size_t I>
class Tmpl {};
int main() {
static_assert(Pack<Tmpl<1>, int>::has_tmpl<Tmpl>());
static_assert(!Pack<int>::has_tmpl<Tmpl>());
}
Now, my question is: How do I test for the presence of a template in a parameter pack without making any assumption how the template looks like? I.e., I don't want to write separate code for template<std::size_t>
, template<std::size_t, typename>
, template <typename, typename>
, etc.
Bonus points if it can be done using the same multiple-inheritance trick.
As @MaxLanghof mentioned in comments, it's not even possible to declare a has_tmpl
that accept arbitrary kinds of templates. It's possible to have overloads of has_tmpl
with different template parameters (template<std::size_t> class
, template<std::size_t, typename> class
, template <typename, typename> class
, etc), but an infinite number of overloads are needed.
Instead, let's use a type that wraps the template, and expose everything we need. The simplest way AFAIK is (ab)using a lambda: []<std::size_t I>(type_identity<Tmpl<I>>){}
.
And then the problem is almost trivial: has_tmpl
can be simply defined to return (std::is_invocable_v<Lambda,type_identity<Ts>> || ...)
.
Complete example:
#include <type_traits>
template<class> struct type_identity {};
template <class... Ts>
struct Pack
{
template<class Lambda>
static constexpr bool has_tmpl(Lambda) {
return (std::is_invocable_v<Lambda, type_identity<Ts>> || ...);
}
};
template<std::size_t I>
class Tmpl {};
int main() {
static_assert(Pack<Tmpl<1>, int>::has_tmpl([]<std::size_t I>(type_identity<Tmpl<I>>){}));
static_assert(!Pack<int>::has_tmpl([]<std::size_t I>(type_identity<Tmpl<I>>){}));
}
Note that this uses a GNU extension that is standardized in C++20 (template-parameter-list for generic lambdas). I don't think this is avoidable.
It should be possible to use multiple inheritance, but fold expression is much shorter ;)
I hope this is what you are looking for (I implemented the Pack
class, without testing your inheritance "trick").
#include <type_traits>
// just for testing
template <typename... Ts> struct Foo {};
template <typename... Ts> struct Bar {};
// compare templates
template <template <typename...> typename, typename>
struct is_same : std::false_type {};
template <template <typename...> typename T, typename... Ts>
struct is_same<T, T<Ts...>> : std::true_type {};
// find templates in list
template <template <typename...> typename T, typename... Pack>
struct searcher
: std::integral_constant<bool, std::disjunction_v<is_same<T, Pack>...>> {};
// your class (only template params changed)
template <class... Ts>
struct Pack {
template <template <typename...> class T>
static constexpr bool has_tmpl() {
return searcher<T, Ts...>::value;
}
};
int main() {
static_assert(Pack<int, long, Foo<int>>::has_tmpl<Foo>());
static_assert(Pack<int, long, Foo<int, short>>::has_tmpl<Foo>());
static_assert(Pack<int, long, Foo<int, short, long>>::has_tmpl<Foo>());
static_assert(!Pack<int, long, long>::has_tmpl<Foo>());
static_assert(!Pack<int, long, Bar<int>>::has_tmpl<Foo>());
static_assert(!Pack<int, long, Bar<int, short>>::has_tmpl<Foo>());
static_assert(!Pack<int, long, Bar<int, short, long>>::has_tmpl<Foo>());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With