10

I want to check if a class is a template specialization of another one. What I have tried is:

template <class T, template <class...> class Template>
struct is_specialization : std::false_type {};

template <template <class...> class Template, class... Args>
struct is_specialization<Template<Args...>, Template> : std::true_type {};

It works fine when all template parameters are type arguments but not when some are non-type arguments. For example it works with std::vector but not std::array (since the later accepts an non-type argument std::size_t).

It's important that the check is made at compile time. Also the solution must work for any template, not just vectors or arrays. That means that it can be any number of type arguments and any number of non-type arguments. For example it should work with template <class A, bool B, class C, int D, class... Args> class foo;

YSC
  • 38,212
  • 9
  • 96
  • 149
user28032019
  • 175
  • 8
  • I'm afraid what you want is not possible: https://stackoverflow.com/q/25893828/5470596 – YSC Mar 28 '19 at 11:56
  • 1
    Catching both `std::vector` and `std::array` can easily be done by adding an `auto...` pack, but that would still only work if all type parameters comes first, and the non-types comes after. Catching a mixed pattern is AFAIK not possible. – super Mar 28 '19 at 12:01
  • @YSC That post is old, in C++17 new features such as `auto` for non-type parameter deduction were introduced, now maybe is possible. I haven't been able to find a way though. – user28032019 Mar 28 '19 at 12:02
  • @super Can you make an answer of that? It's not perfect but maybe it's the nearest I can go. After all, it's usual to put first the type parameters and then the non-type parameters. I can't think of any `std` type that don't follow this rule, can you? – user28032019 Mar 28 '19 at 12:08
  • Hey, I know that code ;) -- I'm giving another shot at it, but don't see a way either. Class template argument deduction looked inspiring but would both require a movable type and be prone to false positives. – Quentin Mar 28 '19 at 12:13
  • @Quentin Yes is your code https://stackoverflow.com/questions/31762958/check-if-class-is-a-template-specialization Can you at least give an answer for the case when the type parameters go first and then the non-type parameters. Another user proposed it but I haven't manage to get it working. – user28032019 Mar 28 '19 at 12:23
  • @user28032019 Seems I spoke too soon. Can't find a way to make it work either. Making a number of specializations all starting with a different number of types, and ending with a auto pack, but then the code is starting to get very smelly. :-) – super Mar 28 '19 at 12:35
  • 1
    I think C++ compilers really _really_ hate mixing types and values (hello most vexing parse) and tries to stay as far away as possible. Or in other words, this is impossible. __Or__ @Quentin manages to abuse the most vexing parse again and wraps everything into one magnificent macro. – Passer By Mar 28 '19 at 12:58
  • 1
    @PasserBy I'm sorry to disappoint, but the parser went through unscathed this time. – Quentin Mar 28 '19 at 13:22

1 Answers1

12

C++20 is a weird, weird world. Cross-checking is welcome as I'm a beginner with CTAD and not entirely sure I've covered all bases.

This solution uses SFINAE to check whether class template argument deduction (CTAD) succeeds between the requested class template and the mystery type. An additional is_same check is performed to prevent against unwanted conversions.

template <auto f>
struct is_specialization_of {
private:
    template <class T>
    static auto value_impl(int) -> std::is_same<T, decltype(f.template operator()<T>())>;

    template <class T>
    static auto value_impl(...) -> std::false_type;

public:
    template <class T>
    static constexpr bool value = decltype(value_impl<T>(0))::value;
};

// To replace std::declval which yields T&&
template <class T>
T declrval();

#define is_specialization_of(...) \
    is_specialization_of<[]<class T>() -> decltype(__VA_ARGS__(declrval<T>())) { }>::value

// Usage
static_assert(is_specialization_of(std::array)<std::array<int, 4>>);

First caveat: Since we can't declare a parameter for the class template in any way without knowing its arguments, passing it around to where CTAD will be performed can only be done by jumping through some hoops. C++20 constexpr and template-friendly lambdas help a lot here, but the syntax is a mouthful, hence the helper macro.

Second caveat: this only works with movable types, as CTAD only works on object declarations, not reference declarations. Maybe a future proposal will allow things such as std::array &arr = t;, and then this will be fixed!

Actually fixed by remembering that C++17 has guaranteed copy-elision, which allows direct-initialization from a non-movable rvalue as is the case here!

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 2
    I'll deduce your template argument. ( I love this solution ) – Hatted Rooster Mar 28 '19 at 13:29
  • @PasserBy I think so. Clang piles up errors on me, but GCC seems to be fine with it. If that weren't the case, I still have the option to use C++20's ability to wrap the lambda inside `decltype` and pass that type instead to reconstruct it later on. – Quentin Mar 28 '19 at 13:29
  • 1
    @SombreroChicken Chicken Tortilla Argument Deduction? – Quentin Mar 28 '19 at 13:30
  • 1
    Just checked, [expr.prim.lambda/2](https://timsong-cpp.github.io/cppwp/n4659/expr.prim.lambda#2) is where it's forbidden, and it's gone in C++20. – Passer By Mar 28 '19 at 13:37
  • @Quentin Oh, that is really smart. The only drawback is that is ugly to use without the macro. Also it don't keep the `std::is_same` syntax. But it works, if nobody finds a better/simpler alternative will be the accepted answer. Thank you very much. – user28032019 Mar 28 '19 at 13:38
  • @user28032019 it could probably be changed to `is_specialization_of(std::array, std::array)`, but I'm not sure whether the class template argument can contain commas or not. – Quentin Mar 28 '19 at 13:40
  • @Quentin: You’d have trouble if you wanted *both* sides to have commas. – Davis Herring Sep 21 '19 at 00:46
  • It shouldn’t matter, but wouldn’t deduction guides be able to break this by suggesting an unrelated or invalid type for the “copy”? – Davis Herring Sep 21 '19 at 00:49
  • @DavisHerring indeed, `Foo(Foo) -> Foo;` breaks it. I don't feel *too* bad about such sabotaged copy constructors not making it through, though. – Quentin Sep 21 '19 at 14:27