Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template template class predicate not working in partial specialization

I have many EnableIf traits that basically check whether the input type satisfies an interface. I was trying to create a generic Resolve trait that can be used to transform those into a boolean trait.

Something like this - https://wandbox.org/permlink/ydEMyErOoaOa60Jx

template <
  template <typename...> class Predicate,
  typename T,
  typename = std::void_t<>>
struct Resolve : std::false_type {};
template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, Predicate<T>> : std::true_type {};

Now if you have an EnableIf trait like so

template <typename T>
using EnableIfHasFoo = std::void_t<decltype(std::declval<T>().foo())>;

You can create a boolean version of that very quickly

template <typename T>
struct HasFoo : Resolve<EnableIfHasFoo, T> {};

Or the analogous variable template.

But for some reason the partial specialization is not working as expected. Resolve does not work as intended. See the output here - https://wandbox.org/permlink/ydEMyErOoaOa60Jx. The same thing implemented "manually" works - https://wandbox.org/permlink/fmcFT3kLSqyiBprm

I am resorting to manually defining the types myself. Is there a detail with partial specializations and template template arguments that I am missing?

like image 955
Curious Avatar asked Nov 28 '18 06:11

Curious


2 Answers

The reason why your approach will fail is that Predicate<T>> in the third template parameter is not a non-deduced context. This causes the deduction to directly fail (see [temp.alias]/2), instead of using the deduced template arguments from elsewhere as in a non-deduced context.

You can wrap your Predicate<T>> to a non-deduced context to make it work:

template<class T>
struct identity {
    using type = T;
};

template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, typename identity<Predicate<T>>::type> : std::true_type {};

Live Demo

Because inside a non-deduced context, the deduction won't happen for Predicate<T> part, instead, it will use the Predicate and T obtained from elsewhere.

As for why the usual detection-idiom (see Guillaume Racicot's answer) will work, it is because std::void_t as a template alias, will be replaced by void in deduction phase (see [temp.alias]/2), thus no deduction will happen.

Here are some examples to illustrate it more clearly:

template<class T>
using always_int = int;

template<template<class> class TT>
struct deductor {};

template<template<class> class TT, class T>
void foo(T, deductor<TT>) {}

template<template<class> class TT, class T>
void bar(T, deductor<TT>, TT<T>) {}

template<class T>
void baz(T, always_int<T>) {}

int main() {
    // ok, both T and TT are deduced
    foo(0, deductor<always_int>{});

    // ERROR, TT<T> is NOT a non-deduced context, deduction failure
    bar(0, deductor<always_int>{}, 0);

    // ok, T is deduced, always_int<T> is replaced by int so no deduction
    baz(0, 0);
}
like image 38
llllllllll Avatar answered Sep 28 '22 05:09

llllllllll


I cannot find the exact reason why your example don't work. If you want to dig more into the details of std::void_t, here's an interesting explanation

Even if I cannot explain it in depth, I would like to add another reliable syntax that is used in the detection idiom.

template<
    template <typename...> class Predicate,
    typename T,
    typename = void>
struct Resolve : std::false_type {};

template <template <typename...> class Predicate, typename T>
struct Resolve<Predicate, T, std::void_t<Predicate<T>>> : std::true_type {};

template <typename T>
using EnableIfHasFoo = decltype(std::declval<T>().foo());

live on compiler explorer

like image 151
Guillaume Racicot Avatar answered Sep 28 '22 07:09

Guillaume Racicot