【问题标题】:Matching variadic non-type templates匹配可变参数非类型模板
【发布时间】:2017-10-31 00:04:33
【问题描述】:

假设我有两个结构,FooBar

template<int...>
struct Foo{};

template<unsigned long...>
struct Bar{};

我想创建一个类型特征(称为match_class),如果我传递两个Foo&lt;...&gt; 类型或两个Bar&lt;...&gt; 类型,则返回true,但如果我尝试混合它们,则返回false:

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_class<f1, f2>::value, "Fail");
    static_assert(match_class<b1, b2>::value, "Fail");
    static_assert(!match_class<f1, b1>::value, "Fail");
}

对于 C++1z(clang 5.0.0 和 gcc 8.0.0),这样做就足够了(Demo):

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

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

但在 C++14 中,我收到以下错误(相同的编译器*Demo):

error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct match_class<S<U...>, S<V...>> : std::true_type{};
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: non-deducible template parameter 'T'
template<class T, template<T...> class S, T... U, T... V>

问题:在 C++14 中有什么解决方法?

理想情况下,测试类型特征的语法应该保持不变。

第二个问题:C++14 的行为是否正确? (或者我看到的 C++17 行为是否未指定?)

*注意,MSVC 19.00.23506 有同样的故障Demo

【问题讨论】:

  • 它只适用于 GCC。很有趣。
  • 更重要的是,它也适用于 clang 4.0.0。可能是 GCC 或 clang 中尚未实现的缺陷。
  • 旁注 - 在 C++17 中你可以使用这个:template&lt;template&lt;auto ...&gt; class S, auto... U, auto... V&gt; struct match_class&lt;S&lt;U...&gt;, S&lt;V...&gt;&gt; : std::true_type{};.
  • @skypjack:说得好。我考虑过这个问题,但由于 auto 模板参数的使用与 C++14 不兼容,我将其省略了:-)

标签: c++ templates c++14 variadic-templates non-type


【解决方案1】:

在 C++14 中,您无法在以下位置推导出 T

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

但在 C++17 中,您可以。您看到的行为是正确的。

在 C++14 中,由于您无法推断出 T,因此您需要一种显式提供它的方法。因此,您可能需要类模板本身来指示它们的非类型模板参数类型:

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

或者对此有一个外部特征。然后显式写出所有内容 - 如果两个类模板具有相同的非类型模板参数 and 则它们匹配,那么它们也具有相同的类模板,按此顺序:

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>
{ };

这是添加对template auto 的支持的结果。在 C++14 中,[temp.deduct.type] 包含:

不能从非类型模板参数的类型推导出模板类型参数。 [例子:

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

-结束示例]

但在 C++17 中,now reads:

当从表达式推导非类型模板形参P所声明的依赖类型对应的实参值时,P类型的模板形参从价值。 [ 示例:

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>

— 结束示例 ]T[N] 类型中N 的类型为std​::​size_­t[ 示例:

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]

— 结束示例 ]

【讨论】:

    【解决方案2】:

    问题:在 C++14 中有什么解决方法?

    C++14 中可能的解决方法是基于特征。
    作为一个最小的工作示例(甚至可能很愚蠢,但它有助于理解这个想法):

    #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");
    }
    

    附带说明,在 C++17 中,您可以将事情简化如下:

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

    关于错误背后的原因,@Barry's answer 包含您需要了解的所有内容(与往常一样)。

    【讨论】:

    • 你认为它可以扩展为非数字非类型模板参数吗?指针/引用?
    • @W.F.嗯,是。只需添加一堆不同的特征来匹配它们。
    • 哦!我现在看到了……对于这种情况,甚至可以将特征类型参数包留空。谢谢!
    【解决方案3】:

    这是一个通用的 C++14 解决方案,它不依赖于手动专门的类型特征或扩展 FooBar

    获取表示其参数类型的类模板的类型的模板元函数:

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

    一个易于使用的别名模板:

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

    然后你可以像这样实现你的match_class trait:

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

    DEMO

    【讨论】:

    • 伙计,我真的不想详尽地指定所有整数类型。向你致敬。
    • 我们还需要为&lt;cstdint&gt; 中的类型提供nontype_template_of 特化
    • 那些只是内置类型的类型定义,所以这样的特化将是重新定义。不过,我确实错过了宽字符类型的特化,如果需要,您可以添加它们。
    • 谢谢。我想切换到 C++17 的另一个令人信服的理由是您不必担心是否记得获得所有这些。
    猜你喜欢
    • 2023-03-07
    • 2023-04-02
    • 2014-06-07
    • 1970-01-01
    • 2016-10-05
    • 1970-01-01
    • 2017-03-18
    • 2023-04-02
    相关资源
    最近更新 更多