【问题标题】:Can variadic template template parameter be partial-specialized?可变参数模板模板参数可以部分专业化吗?
【发布时间】:2018-07-31 07:50:41
【问题描述】:

考虑以下程序:

template<template<typename ...> class>
struct foo {};

template<template<typename> class C>
struct foo<C> {};

int main() {}

Clang 以错误方式拒绝它:

类模板部分特化不特化任何模板参数

即使在最新的 clang 7.0 HEAD 中,请参阅演示 here。但是,gcc accepts it

请参阅[temp.class.spec],其中说明了部分专业化的规则,我找不到任何禁止该模板的部分专业化的内容。尤其是特化确实是more specialized,错误信息看起来不对。

编辑:

但是gcc的行为也是异常的,考虑下面的程序:

#include <iostream>

template<template<typename ...> class>
struct foo { void show() { std::cout << "Primary.\n"; } };

template<template<typename> class C>
struct foo<C> { void show() { std::cout << "Specialized.\n"; } };

template<class...> struct bar {};

int main() {
    foo<bar> f;
    f.show();
}

原来gcc在这种情况下使用了专门的版本,见here

现在我想问:

  • 标准允许这种部分特化吗?

  • 哪个编译器是正确的? (一个/全部/没有?)

【问题讨论】:

  • 如果这种特化是有效的,那么 foo&lt;bar&gt;template&lt;typename...&gt; struct bar {}; 的实例化是不明确的,不是吗?
  • @NikitaKniazev 对我来说,这不是模棱两可的,它应该是主要的 foo 被使用。但它似乎更复杂,请参阅 gcc here 的奇怪行为。它无条件使用专用版本...
  • 关于您的 ecample foo&lt;bar&gt; f; f.show()... 如果您编译 C++17,请注意打印“specialized”,如果您编译 C++14,请注意打印“primary”; C++17 中关于模板-模板匹配发生了一些变化(但我并不是说 g++ 是对的……我真的非常困惑)。
  • @Walter clang 5.0 和 clang 7.0 HEAD。现场演示存在问题。
  • @max66 我不知道如何解释......但我认为如果它的格式不正确,应该在标准中的某个地方说明它......我找不到任何东西。

标签: c++ templates clang language-lawyer template-specialization


【解决方案1】:

这个finaly似乎是新C++模板模板参数推导partial support for this feature实现中的一个GCC bug。

要确定部分特化是否比类模板更特化,partial ordering is applied to 2 corresponding synthetized functions

//template class:
template<template<class...>class P> void f_foo0(foo<P>);

//Partial specialization
template<template<class P> class P> void f_foo_partial0(foo<P>);

然后尝试执行模板参数推导,通过调用每个函数的参数对应于另一个函数参数(参见[temp.func.order]

template<class P> struct Atype{};
template<class ...P> struct ATypePack{};

//Is f_foo_partial at least as specialized as f_foo?
f_foo(foo<AType>{});

//Is f_foo at least as specialized as f_foo_partial?
f_foo_partial(foo<ATypePack>{});

如果 (1) 的模板参数成功,则 f_foo_partial 至少与 f_foo 一样专业。如果 (2) 的模板参数成功,则 f_foo 至少与 f_foo_partial 一样专业。(参见 [temp.deduct.partial])。那么如果只有一个至少和另一个一样专业,那么它就是更专业的那个。

所以to check if template argument is deductible,然后deduction of template argument from a type 被执行。

然后为了执行此匹配,应用 C++17 中引入的规则[temp.arg.template]/3

当 P 至少与模板参数 A 一样特化时,模板参数匹配模板模板参数 P。[...]

并且 [temp.arg.template]/4 指定将使用这两个发明的函数与前面的情况类似地执行此排序:

template<class...> struct X{};
//for the argument
template<class...P> void f_targ(X<P...>);

//Partial specialization
template<class P> void f_tparam(X<P>);

struct Atype{};
struct ATypePack{};

//Is template template parameter at least as specialized template template arg?
f_targ(X<AType>{});

//Is template template arg at least as specialized as template template parameter?
f_tparam(X<ATypePack>{});
  • 对于 (1) 模板参数继承 ...P`` isAType` 的推导参数。

  • 对于 (2) 有一个特殊规则,仅适用于模板偏序 [temp.deduct.type]/9.2:

[...] 在偏序期间,如果 Ai 最初是一个包扩展:

  • 如果 P 不包含对应于 Ai 的模板参数,则 Ai 被忽略;

  • 否则,如果 Pi 不是包展开,则模板参数推导失败。

这里Ai是ATypePackPitemplate&lt;class P&gt; void p_foo_partial(foo&lt;P&gt;)函数参数中的P

因此,由于以粗体引用的这条规则,模板实参推导对 (2) 失败,因此模板模板形参“类 P”比其实参更专业。所以f_foo_partial(foo&lt;ATypePack&gt;{}) 的调用格式正确。

另一方面,同样的规则,对f_foo(AType{}) 的调用格式良好,因为 [temp.arg.temp]/3:

[...] 如果 P 包含参数包,则如果 A 的每个模板参数都与 P 的模板参数列表中的相应模板参数匹配,则 A 也匹配 P。[...]

所以f_foo_partial 并不比f_foo 更特化,所以template&lt;class&lt;class &gt; class C&gt; struct foo&lt;C&gt;; 不是模板foo 的部分特化。

【讨论】:

  • 用于偏序的合成函数模板应该是template&lt;template&lt;class...&gt; class C&gt; void f(foo&lt;C&gt;);template&lt;template&lt;class&gt; class C&gt; void f(foo&lt;C&gt;);
  • @bogdan 我正在研究它,结果会一样,还有一层推理......这太长了!
  • P0522 的新规则使f_foo 至少与f_foo_partial 一样专业(f_foo_partial(foo&lt;ATypePack&gt;{}) 在新规则下成功)。另一方面,f_foo_partial 至少与 f_foo 一样专业,因为 [temp.arg.template]/3 的其余部分是从 C++14 继承而来的。
  • [temp.arg.template]/3 只表示参数是否匹配对应的参数,关键是如何推断参数。
  • 我不知道。该标准似乎没有指定如何推断模板模板参数。因此我更喜欢template-name&lt;TT&gt; 是非演绎的。
【解决方案2】:

很遗憾不是这样……

  • 我绝对不想尝试进入标准决定不允许以这种方式表达的部分专业化的“为什么”——(或者如果它的特定措辞已被遗忘)。
  • 这里有些人无疑比我更擅长解读标准措辞的复杂微妙之处......;)

也就是说,...

  • 如果有人正在寻找解决此问题的方法 - (完全标准的方法) - 这里的信息和示例很可能有助于解决您的问题...


° 模板参数个数。

  • 在展示可以实现基于模板模板参数的参数数量的特化的方法之前,我将简要介绍一下模板参数本身。

(我可能需要更正措辞,但要点应该足够清楚)

使用模板的重点:

  • 在使用别名时。
  • 带或不带参数。
  • 在调度特征内。
  • 在检测特征内。
  • 在专业范围内。

是关于何时必须调用它接收的类型参数,以及何时调用它们参数强>...

一开始你可能会觉得很明显,但你也可能会惊讶地发现我已经发布了一个非常详细的解决方案,回答了另一个问题,以检测声明的参数的数量 通过模板。

该解决方案通过首先尝试'计数' 传递给模板的参数 来描述搜索过程。最后,由于解决方案中的所有内容从未完全定义的模板的前向声明上完成,我意识到这是检测算法,因为在没有任何Victim模板永远实例化的情况下获得结果...

  • 详解及实现:here


°模板模板参数特化。

(回到最初的疑惑)

当我发现这个问题时,我试图做与问题的第一个代码示例中显示的完全相同类型的专业化,以创建一些实现选择器之王......

我对自己说:“好吧,看来不能那样做……”

由于我已经对模板声明的参数数量进行了检测(由上面的链接引用),因此我很快找到了一种产生所需效果的方法。我认为它肯定对其他人有用,因此我决定将其发布在这里...

我认为代码是不言自明的,因此不需要更多解释。

// Helper to make the static_assert 'name-dependent'.
template<typename...> using AlwaysFalse = std::false_type;

// Primary template should only signal the error...
template<typename Trait>
struct ImplSelect {
    static_assert(
        AlwaysFalse<Trait>::value,
        "Missing specialization..."
    );
};

// Specialization for a template with one type parameter.
template<template <typename> class Trait, typename T>
struct ImplSelect<Trait<T>> { using Type = Trait<int>; };

// Specialization for a template with two type parameters.
template<template <typename, typename> class Trait, typename T, typename U>
struct ImplSelect<Trait<T, U>> { using Type = Trait<int, int>; };

/* [ Note ] These are ONLY forward declarations.
 *          They aren't instatiated within this code.
 */
template<typename T>             struct Victim1;
template<typename T, typename U> struct Victim2;

/* [ Note ] Opaque type tags (forward declarations).
 *          Allows for further tag dispatching if desired.
 */
struct Tag1; struct Tag2;

/* Selection of Victim1<int> and Victim2<int, int>.
 * Not instantiated yet...
 */
using Impl1 = typename ImplSelect<Victim1<Tag1>>::Type;
using Impl2 = typename ImplSelect<Victim2<Tag1, Tag2>>::Type;
享受吧!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多