Matching variadic non-type templates

后端 未结 3 1911
予麋鹿
予麋鹿 2021-02-18 14:13

Let\'s say I have two structs, Foo and Bar:

template
struct Foo{};

template
struct Bar{};


        
相关标签:
3条回答
  • 2021-02-18 14:29

    In C++14, you could not deduce T in:

    template<class T, template<T...> class S, T... U, T... V>
    struct match_class<S<U...>, S<V...>> : std::true_type{};
    

    but in C++17, you can. The behavior you see is correct.

    In C++14, since you cannot deduce T, you need a way of explicitly providing it. So you might require the class templates themselves to indicate their non-type template parameter type:

    template <int...> struct Foo { using type = int; };
    template <unsigned long...> struct Bar { using type = unsigned long; };
    

    Or have an external trait for this. And then explicitly write out everything - two class templates match if they have the same non-type template parameter and then also have the same class template, in that order:

    template <class... Ts> struct make_void { using type = void; };
    template <class... Ts> using void_t = typename make_void<Ts...>::type;
    
    template <class T1, class T2, class A, class B>
    struct match_class_impl : std::false_type { };
    
    template <class T, template <T...> class S, T... U, T... V>
    struct match_class_impl<T, T, S<U...>, S<V...>> : std::true_type{};
    
    template <class A, class B, class=void>
    struct match_class : std::false_type { };
    
    template <class A, class B>
    struct match_class<A, B, void_t<typename A::type, typename B::type>>
        : match_class_impl<typename A::type, typename B::type, A, B>
    { };
    

    This is a consequence of adding support for template auto. In C++14, [temp.deduct.type] contained:

    A template type argument cannot be deduced from the type of a non-type template-argument. [Example:

    template<class T, T i> void f(double a[10][i]);
    int v[10][20];
    f(v); // error: argument for template-parameter T cannot be deduced
    

    -end example]

    But in C++17, it now reads:

    When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value. [ Example:

    template<long n> struct A { };
    
    template<typename T> struct C;
    template<typename T, T n> struct C<A<n>> {
      using Q = T;
    };
    
    using R = long;
    using R = C<A<2>>::Q;           // OK; T was deduced to long from the
                                    // template argument value in the type A<2>
    

    — end example ] The type of N in the type T[N] is std​::​size_­t. [ Example:

    template<typename T> struct S;
    template<typename T, T n> struct S<int[n]> {
      using Q = T;
    };
    
    using V = decltype(sizeof 0);
    using V = S<int[42]>::Q;        // OK; T was deduced to std​::​size_­t from the type int[42]
    

    — end example ]

    0 讨论(0)
  • 2021-02-18 14:41

    Question: What is a workaround for this in C++14?

    A possible workaround in C++14 is based on traits.
    As a minimal, working example (maybe even stupid, but it helps getting the idea):

    #include <type_traits>
    #include <utility>
    
    template<int...>
    struct Foo{};
    
    template<unsigned long...>
    struct Bar{};
    
    template<typename>
    struct traits;
    
    template<int... V>
    struct traits<Foo<V...>> { using type = Foo<0>; };
    
    template<unsigned long... V>
    struct traits<Bar<V...>> { using type = Bar<0>; };
    
    template<typename T, typename U>
    constexpr bool match = std::is_same<typename traits<T>::type, typename traits<U>::type>::value;
    
    int main() {
        using f1 = Foo<1, 2, 3>;
        using f2 = Foo<1>;
        using b1 = Bar<1, 2, 3>;
        using b2 = Bar<1>;
    
        static_assert(match<f1, f2>, "Fail");
        static_assert(match<b1, b2>, "Fail");
        static_assert(!match<f1, b1>, "Fail");
    }
    

    As a side note, in C++17 you can simplify things up as it follows:

    template<template<auto ...> class S, auto... U, auto... V>
    struct match_class<S<U...>, S<V...>> : std::true_type{};
    

    About the reasons that are behind the error, @Barry's answer contains all what you need to understand it (as usual).

    0 讨论(0)
  • 2021-02-18 14:49

    Here is a general C++14 solution that does not rely on manually specialized type traits or extending Foo and Bar.

    A template metafunction that obtains a type representing the class template of its argument type:

    namespace detail
    {
        // Type representing a class template taking any number of non-type template arguments.
        template <typename T, template <T...> class U>
        struct nontype_template {};
    }
    
    // If T is an instantiation of a class template U taking non-type template arguments,
    // this has a nested typedef "type" that is a detail::nontype_template representing U.
    template <typename T>
    struct nontype_template_of {};
    
    // Partial specializations for all of the builtin integral types.
    template <template <bool...> class T, bool... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<bool, T>; };
    template <template <char...> class T, char... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<char, T>; };
    template <template <signed char...> class T, signed char... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<signed char, T>; };
    template <template <unsigned char...> class T, unsigned char... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned char, T>; };
    template <template <short...> class T, short... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<short, T>; };
    template <template <unsigned short...> class T, unsigned short... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned short, T>; };
    template <template <int...> class T, int... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<int, T>; };
    template <template <unsigned int...> class T, unsigned int... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned int, T>; };
    template <template <long...> class T, long... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<long, T>; };
    template <template <unsigned long...> class T, unsigned long... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned long, T>; };
    template <template <long long...> class T, long long... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<long long, T>; };
    template <template <unsigned long long...> class T, unsigned long long... Vs>
    struct nontype_template_of<T<Vs...>> { using type = detail::nontype_template<unsigned long long, T>; };
    

    An alias template for ease of use:

    // Alias template for nontype_template_of.
    template <typename T>
    using nontype_template_of_t = typename nontype_template_of<T>::type;
    

    Then you can implement your match_class trait just like this:

    template <class A, class B>
    struct match_class : std::is_same<nontype_template_of_t<A>, nontype_template_of_t<B>> {};
    

    DEMO

    0 讨论(0)
提交回复
热议问题