【问题标题】:Is this "if e is a pack, then get a template name, otherwise get a variable name" valid or not?这是“如果 e 是一个包,则获取模板名称,否则获取变量名称”是否有效?
【发布时间】:2017-03-17 09:56:36
【问题描述】:

我尝试构建一个不需要 typenametemplate 的案例,但仍会根据给定名称 t 是否为函数来生成变量或模板参数包与否

template<typename T> struct A { template<int> static void f(int) { } }; 
template<typename...T> struct A<void(T...,...)> { static const int f = 0; }; 
template<typename> using type = int; 

template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }

int main() {
   f(1);   
}

以上将参考static const int,并做一个对比。以下只是将T t 更改为一个包并使f 引用一个模板,但GCC 也不喜欢

template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }

int main() {
   f(1, 2, 3);   
}

GCC 第一次抱怨

main.cpp:5:68: error: incomplete type 'A<void(type<decltype (t)>, ...)>' used in nested name specifier
 template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }

第二个

 main.cpp:5:74: error: invalid operands of types '<unresolved overloaded function type>' and 'int' to binary 'operator<'
  template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }

我有多个问题

  • 以上代码是否根据语言工作,还是有错误?
  • 由于 Clang 接受这两种变体但 GCC 拒绝,我想问什么编译器是正确的?
  • 如果我删除了主模板的主体,那么对于 f(1, 2, 3) 的情况,Clang 会抱怨

    main.cpp:5:42: error: implicit instantiation of undefined template 'A<void (int)>'
    

    请注意,上面写着A&lt;void (int) &gt;,而我期望的是A&lt;void (int, int, int)&gt;。这种行为是如何发生的?这是我的代码中的错误 - 即它是错误的,还是 Clang 中的错误?我似乎记得一个关于扩展顺序与别名模板替换的缺陷报告,这是否相关,是否会使我的代码格式错误?

【问题讨论】:

  • 对我来说 void(T...,...) 看起来像 T... 在非推断上下文中,但我不确定编译器应该如何反应......
  • @W.F.我在“is_function”特征实现中看到了这一点,以检测具有该模式的函数。如果这不能推断出T,我会感到惊讶,因为我不确定is_function 应该如何检测它们。
  • 不知道我需要在那里看看
  • ftemplate 的情况下,为什么在::f 之间不需要template
  • @Yakk 我的印象是type&lt;dependent-type&gt; 不依赖于template&lt;typename&gt; using type = int;,但我可能是错的。见open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390。问这个问题的时候忘记了很久以前做的那个DR了,现在才发现其实还没有解决。委员会只是澄清他们希望它是非依赖的。但是,除此之外,我的 sn-p 还有什么问题吗?

标签: c++ c++11 language-lawyer


【解决方案1】:

扩展参数包应该或确实使表达式类型依赖。不管扩展的东西是否依赖于类型。

如果没有,那么 C++ 的类型依赖规则中就会有一个漏洞,这将是标准中的缺陷。

所以A&lt;void(type&lt;decltype(t)&gt;...)&gt;::ft 是一个包时,无论你在void( 这里拉) 部分来解包t,都应该是一个依赖类型,并且template 是必需的如果是template,则在f 之前。

t 不是包的情况下,有意 type&lt;decltype(t)&gt; 不依赖(参见http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390),但标准可能会或可能不会在这一点上达成一致(我认为不是?)

如果编译器做了“委员会的意图”,那么当 t 不是一个包时:

A<void(type<decltype(t)>...)>::f<0>(1)

可能意味着

A<void(int...)>::f<0>(1)

这是

A<void(int, ...)>::f<0>(1)

如果ftemplate(您的代码使它成为int,但我认为交换两者应该可以)这很好。但该标准目前显然不同意?

因此,如果实现了 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390,那么您可以交换您的两个 A 专业化。 void(T...,...) 特化应该有一个template&lt;int&gt; void f(int)T 特化应该有一个static const int

现在在A&lt;&gt; 依赖于(取决于包的大小)的情况下,::fint 并且不需要template。在A&lt;&gt; 不依赖的情况下,::ftemplate,但不需要消歧。

我们可以将type&lt;decltype(t)&gt;...替换为:

decltype(sizeof(decltype(t)*))...

并且sizeof(decltype(t)*) 是非依赖类型(它是std::size_t),decltype 给了我们std::size_t... 被视为老派... arg。这意味着void(std::size_t...) 变为非依赖类型,因此A&lt;void(std::size_t...)&gt; 不依赖,因此::f 作为模板在依赖上下文中不是模板。

如果t 是具有一个元素的参数包

decltype(sizeof(decltype(t)*))...

变成

std::size_t

在依赖上下文中t 包中每个元素一个副本)。所以我们得到

A<void(std::size_t)>::f

假定是一个标量值,所以

A<void(std::size_t)>::f<0>(1)

变成一个表达式,计算结果为false

(在原始问题的 cmets 中与 Johannes 讨论时产生的逻辑链)。

【讨论】:

  • 很好,这甚至提供了一个不再需要template 的解决方案。是否应该针对不要求 template 并给出奇怪的 A&lt;void(int)&gt; 错误消息的 clang 提交错误报告?
  • “包扩展使其依赖”-DR 是open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2024
  • 这些是值得在 StackOverflow 上积极参与的问答。谢谢你的课。 +1
【解决方案2】:

您的第二个案例格式不正确; A&lt;void(type&lt;decltype(t)&gt;...)&gt;::f&lt;0&gt;(1) 应该是

A<void(type<decltype(t)>...)>::template f<0>(1)
//                             ~~~~~~~~~

对于第一种情况,两个编译器的行为都不正确;这被认为足够令人困惑,以至于提出CWG 1520 来查询正确的行为;结论是应该在别名替换之前应用包扩展:

后一种解释(专业列表)是正确的解释;参数包不能替代任何东西,包括别名模板特化。 CWG 认为这在目前的措辞中已经足够清楚了。

这让人想起 CWG 1558(别名模板和 SFINAE),它是为 C++14 修复的,但根据上述内容,即使 C++11 编译器也有望得到正确的结果,因此 gcc 和clang 弄错了(尽管公平地说,它们在更简单的情况下确实表现正确,包括 CWG 1520 中的激励示例)。请注意,MSVC 直到最近才拥有a similar bug;它在 VS2015 中已修复。

您的代码(仅在第一种情况下)是正确的;但作为一种解决方法,您可以更改别名模板以使用和丢弃其模板参数,为两个编译器修复您的程序 - 当然这意味着您的 CWG 1390 漏洞利用将不再有效:

template<typename T> using type = decltype(((int(*)(T*))(0))(0)); // int

但是,我不认为您的 CWG 1390 技巧可以像介绍的那样工作,因为即使 type&lt;decltype(t)&gt;... 的扩展替换不依赖于 t...类型,它也是取决于他们的编号

template<typename T> struct A { template<int> static void f(int) {} }; 
template<> struct A<void(int, int, int)> { static const int f = 0; }; 

正如 Yakk 所指出的,如果你交换成员函数模板和数据成员,它就可以工作,因为数据成员在依赖上下文中是可以的。

【讨论】:

  • “使用和丢弃”的好技巧,让两个编译器都对我的第一个案例感到满意。第二种情况需要template 让我很难过。如果我理解正确,如果 open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390 得到了委员会评论的要求,那么案例 2 也将有效,没有 template?
  • @JohannesSchaub-litb 是的。
  • t 是一个包含一个元素的包... 时,type&lt;decltype(t)&gt;... 扩展为int,因此子句缩减为A&lt;void(int)&gt;::f&lt;0&gt;(1)。在A&lt;void(int)&gt; 中,f 不是template,而是int。所以template 在这里是错误,因为f 不是template。那么0&lt;0&gt;(0) 只是0&lt;0(为假)的序列,然后是false&gt;(1)(为假)。还是我在这里遗漏了什么?
  • @Yakk 省略号在右尖括号的另一边。
  • @ecatmur 评论中的错字。固定的。结论不变?哦,天哪,这里是 template,而不是 int。叹。我的专业倒退了。
猜你喜欢
  • 2019-08-24
  • 1970-01-01
  • 2015-06-15
  • 2023-03-31
  • 2018-06-16
  • 2022-01-11
  • 2016-06-22
  • 2015-12-09
  • 2020-07-17
相关资源
最近更新 更多