为什么模板<typename ...>无法通过模板<template <typename> typename&gt;识别为可实例化?

时间:2017-02-05 04:43:27

标签: c++ c++11 templates gcc variadic-templates

我试图随意&#34;绑定&#34;模板参数但遇到了优雅问题。

为了直接解决根本问题,gcc 6.2存在以下问题,但从逻辑上讲,我认为没有问题...

template<template<typename, typename> P, typename A, typename B>
struct foo {
    static constexpr bool value = P<A, B>::value;
};

template<typename...Ts>
struct bar {
    static constexpr bool value = true;
};

... foo给定bar,例如foo<bar, void, void>会导致bar<void, void>(有效)的实例化,value true }成员为foo<bar, void, void>::value,因此true同样为struct bar<void, void> { static constexpr bool value = true; }; struct foo<bar, void, void> { static constexpr bool value = bar<void, void>::value; //which is true }; 有效地,这个应该(在我看来)导致实例化的结构在概念上类似于...

template<typename...>
struct type_list { };

template<template<typename...> typename Tmpl, typename...Ts>
struct bind_template {
    template<typename...Us>
    using type = Tmpl<Ts..., Us...>;
};

template<template<typename> typename Predicate, typename...Ts>
struct has_matching_type {
    private:
        template<template<typename> typename, typename, typename=void>
        struct helper: std::false_type { };
        template<template<typename> typename P, typename U, typename...Us>
        struct helper<P, type_list<U, Us...>, typename std::enable_if<P<U>::value>::type>: std::true_type { };
        template<template<typename> typename P, typename U, typename...Us>
        struct helper<P, type_list<U, Us...>, typename std::enable_if<!P<U>::value>::type>: helper<P, type_list<Us...>> { };
    public:
        static constexpr bool value = helper<Predicate, type_list<Ts...>>::value;
};

template<typename T, typename...Ts>
using has_type = has_matching_type<bind_template<std::is_same, T>::template type, Ts...>;

你可以在这里看到这个概念(或者更确切地说是错误)https://godbolt.org/g/lT9umg

回到现在开始,首先我尝试了以下......

has_type<T, Ts...>

稍后我可能尝试通过cout << has_type<long, int, bool, long, float>::value << endl; 进行实例化,例如......

bound_template

然而,正如我指出gcc 6.2.0抱怨因为它似乎没有意识到这一点,一旦完成解析,模板实例化在实用上是等效的。

只需知道模板参数的数量并专门针对该确切数字即可解决问题。如果我专注std::is_same<LHS, RHS>,请记住template<template<typename, typename> typename Tmpl, typename T> struct bind_template<Tmpl, T> { template<typename U> using type = Tmpl<T, U>; }; ...

bind_template<std::is_same, long>::type

...我们突然编译并评估编译时没问题,因为gcc认为template<template<typename...> class Tmpl, typename... Ts> struct bind_helper { template<typename... Us> struct type: Tmpl<Ts..., Us...> { }; template<typename A> struct type<A>: Tmpl<Ts..., A> { }; template<typename A, typename B> struct type<A, B>: Tmpl<Ts..., A, B> { }; template<typename A, typename B, typename C> struct type<A, B, C>: Tmpl<Ts..., A, B, C> { }; }; 完全采用了一个类型参数。

显然,将这​​个概念抽象出来以允许任何模板参数,例如积分常数而不仅仅是类型,无论编译器如何都是一个基本问题。仅仅关注一分钟的类型,我的问题是多方面的:

  1. 我是否在概念上遗漏了一些内容,编译器实际上正在做的事情对我来说应该是显而易见的?
  2. 如果没有,这是否违反C ++ 11标准,不是标准指示,还是编译器依赖?
  3. 无论前两个问题的答案是什么,我都可以通过一些优雅的方式解决这个问题吗?
  4. 从功能上讲,真正的问题(特别是如果这是C ++ 11中不可避免的问题)是......

    是否有一些优雅的方式我可以抽象地绑定模板而不必专门针对每种情况(这里最简单的是 n 类型的数量)?

    能够得到问题1或3的答案会很棒。问题3是最重要的,因为在一天结束时,重要的是什么。

    显然,我可以专攻(如上图所示)。但是一个很大的问题是即使以下情况似乎也不起作用(至少根据this online compiler)......

    bind_template

    这意味着,我不仅要生成一堆参数,还要通过完整的std::bind专业化来匹配外部参数。这很快就会成为(实际上)二项式问题。

    进一步扩展这个概念(但仍然保持类型),我计划下一步实施&#34;占位符&#34;同样的方式Parking使用占位符(它本来相当优雅,因为我只是剥离并重新加入索引列表)。显然,如果没有更抽象的方法,这就太麻烦了。

2 个答案:

答案 0 :(得分:10)

这在C ++ 17中得到修复。具体来说,来自14.3.3 "Template template arguments", paragraph 3:

template<class T> class A { /* ... */ };
template<class T, class U = T> class B { /* ... */ };
template<class ... Types> class C { /* ... */ };
template<auto n> class D { /* ... */ };
template<template<class> class P> class X { /* ... */ };
template<template<class ...> class Q> class Y { /* ... */ };
template<template<int> class R> class Z { /* ... */ };

X<A> xa;            // OK
X<B> xb;            // OK
X<C> xc;            // OK
Y<A> ya;            // OK
Y<B> yb;            // OK
Y<C> yc;            // OK
Z<D> zd;            // OK

此处的相关示例为X<C>。这适用于带有标记-std=c++1z的g ++ 7。

C ++ 14指定上面的示例格式不正确:

template<class T> class A { /∗ ... ∗/ };
template<class T, class U = T> class B { /∗ ... ∗/ };
template <class ... Types> class C { /∗ ... ∗/ };
template<template<class> class P> class X { /∗ ... ∗/ };
template<template<class ...> class Q> class Y { /∗ ... ∗/ };
X<A> xa; // OK
X<B> xb; // ill-formed: default arguments for the parameters of a template argument are ignored
X<C> xc; // ill-formed: a template parameter pack does not match a template parameter

这一变化发生在2016年末的论文DR: Matching of template template-arguments excludes compatible templates。此更改已应用于this commit from November。问题是known by the committee since 1999 or before

答案 1 :(得分:3)

在C ++ 17之前,由于jbapple's answer中提到的令人遗憾的语言缺陷,此代码原样不正确。

符合C ++ 11标准的解决方案是从使用各地的元函数切换到在任何地方使用元函数类。来自Boost.MPL定义的元函数类将是具有名为apply的模板别名的类型。我们可以这样调用:

template <class MFC, class... Ts>
using apply_t = typename MFC::template apply<Ts...>;

我们可以通过以下方式将模板提升为元函数类:

template <template <typename...> class Z>
struct quote {
    template <class... Args>
    using apply = Z<Args...>;
};

然后让我们重写bind_template来获取元函数类而不是模板:

template <class MFC, class... Ts>
struct bind_template {
    template <class... Us>
    using apply = apply_t<MFC, Ts..., Us...>;
};

然后重写has_matching_type以获取元函数类而不是模板:

template<class Predicate, class... Ts>
struct has_matching_type {
private:
    template<class>
    struct helper: std::false_type { };

    template<typename U, typename...Us>
    struct helper<type_list<U, Us...>>
        : std::conditional<
            apply_t<Predicate, U>::value,
            std::true_type,
            helper<type_list<Us...>>
            >::type
     { };

public:
    static constexpr bool value = helper<type_list<Ts...>>::value;
};

template<class T, class... Ts>
using has_type = has_matching_type<bind_template<quote<std::is_same>, T>, Ts...>;

现在你的初始has_type<long, int, bool, long, float>::valuetrue,即使在C ++ 11中也是如此。