Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++17: Generic (multiple-inheritance based?) check for template in parameter pack

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.

like image 948
Lukas Barth Avatar asked Jun 19 '19 10:06

Lukas Barth


Video Answer


2 Answers

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

like image 128
cpplearner Avatar answered Oct 01 '22 09:10

cpplearner


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>());
}
like image 28
local-ninja Avatar answered Oct 01 '22 09:10

local-ninja