9

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.

Testing for a Class

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

Testing for a Specific Template

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

The Question

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
Lukas Barth
  • 2,734
  • 18
  • 43
  • 2
    I don't think you can match a template of unknown signature against anything (except concrete instantiations of course). You can have template template parameters but only for known template signatures. You similarly can't match a function template against anything (except, again, something that deduces the instantiation to use). But I'd be happy to be proven wrong. – Max Langhof Jun 19 '19 at 11:04
  • 6
    It looks like this is impossible. – n. m. could be an AI Jun 19 '19 at 11:07
  • 3
    In other words, the issue here is first and foremost the lack of an "interface" for passing arbitrary templates (or function templates) to such a "tester". If each template to be tested was wrapped in a type (and had a fixed name in that type - compare `operator()` for functors) then things would look differently, because then you could pass these templates around (and not just their instantiations). But as it stands you've arrived at one of the limitations of the C++ templating system. – Max Langhof Jun 19 '19 at 11:11
  • not sure if I understood the question correctly, but is what you're asking the same thing as the `is_any` template function answer to [this other question](https://stackoverflow.com/questions/17032310/how-to-make-a-variadic-is-same)? – xception Jun 19 '19 at 12:07
  • 2
    Note that this approach fails if you have multiple options, e.g. `static_assert(Pack, int, Tmpl<2>>::has_tmpl());` – Barry Jun 19 '19 at 12:18
  • @xception: No, that `is_any` only works for classes, not templates, right? – Lukas Barth Jun 19 '19 at 13:32
  • works for classes as well as templated classes, but not templates with a undefined type if that's what you're looking for... – xception Jun 19 '19 at 14:20
  • @Barry: It should be pretty easy to repair it by declaring each function template in an indirect base class instantiated with the class template detected via partial specialization. – Davis Herring Jun 19 '19 at 15:21

2 Answers2

3

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

cpplearner
  • 13,776
  • 2
  • 47
  • 72
1

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>());
}
local-ninja
  • 1,198
  • 4
  • 11