使用签名中的另一个成员模板函数定义行外成员模板函数

Posted

技术标签:

【中文标题】使用签名中的另一个成员模板函数定义行外成员模板函数【英文标题】:Out-of-line member template function definition using another member template function in the signature 【发布时间】:2017-08-04 16:08:55 【问题描述】:

我在一些实际的 C++11 代码中遇到了这个问题,但我将其归结为:

template<int i> struct Dummy ;

template<typename T>
struct Foo 
  template<int i> static constexpr int bar()  return i; 

  template<int i>
  static auto working() -> Dummy<bar<i>()>;

  template<int i>
  static auto also_working() -> Dummy<Foo<T>::template bar<i>()>;

  template<int i>
  static Dummy<Foo<T>::template bar<i>()> not_working();
;

template<typename T> template<int i>
auto Foo<T>::working() -> Dummy<bar<i>()> 
  return Dummy<bar<i>()>;


template<typename T> template<int i>
auto Foo<T>::also_working() -> Dummy<Foo<T>::template bar<i>()> 
  return Dummy<bar<i>()>;


template<typename T> template<int i>
Dummy<Foo<T>::template bar<i>()> Foo<T>::not_working() 
  return Dummy<bar<i>()>;

我试图为模板类的模板成员函数创建一个外联定义,其中函数的签名涉及调用另一个模板成员函数,并从not_working() 函数之类的东西开始。问题是定义与声明不匹配。

叮当说:

clang++ -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -std=c++11   -c -o out_of_line.o out_of_line.cc
out_of_line.cc:28:42: error: out-of-line definition of 'not_working' does not match any declaration in 'Foo<T>'
Dummy<Foo<T>::template bar<i>()> Foo<T>::not_working() 
                                         ^~~~~~~~~~~

GCC 说:

g++ -Wall -Wextra -pedantic -std=c++11   -c -o out_of_line.o out_of_line.cc
out_of_line.cc:28:34: error: prototype for ‘Dummy<bar<i>()> Foo<T>::not_working()’ does not match any in class ‘Foo<T>’
 Dummy<Foo<T>::template bar<i>()> Foo<T>::not_working() 
                                  ^~~~~~
out_of_line.cc:14:43: error: candidate is: template<class T> template<int i> static Dummy<Foo<T>::bar<i>()> Foo<T>::not_working()
   static Dummy<Foo<T>::template bar<i>()> not_working();
                                       ^~~~~~~~~~~

通过反复试验,我发现使用尾随返回类型可以获得与声明匹配的定义,从而产生also_working() 函数。在那里我意识到,由于尾随返回类型中范围的变化,我可以取消一些名称限定,从而产生更漂亮的working() 函数。

现在我想知道为什么not_working() 函数不起作用,即为什么它的定义与其声明不匹配(我可以对我找到的解决方案一无所知,但我可能会遇到更多此类问题,我不想浪费更多时间反复试验);该错误是在编译器中还是在我的代码中。我已通读 14.6 名称解析 [temp.res],但我不确定适用于这种情况的规则。

澄清问题:鉴于 C++11 标准中的规则:

    not_working() 定义是否应与声明匹配? 确定 1.涉及哪些规则? 在确定 1. 时,来自 2. 的规则如何交互?

【问题讨论】:

奇怪但真实...使用 C++14 变量模板 compiles fine with g++, but still doesn't compile with clang++ 的相同代码。对我来说看起来像是一个编译器错误。 虽然没有解决这个特定问题,this core language issue 包含一个答案,暗示其目的是让离线定义与文本匹配的签名匹配声明。 auto 可以帮助您处理无法编写的类型的地方之一。据我所知,gcc 和 clang 都对 auto Foo&lt;T&gt;::working(); 感到满意,并且会在需要时推断返回类型。 【参考方案1】:

看起来它正在尝试实现CWG2,但可能以令人惊讶的顺序做事。查看 gcc 的错误:

prog.cc:28:34: error: prototype for 'Dummy<bar<i>()> Foo<T>::not_working()' does not match any in class 'Foo<T>'
 Dummy<Foo<T>::template bar<i>()> Foo<T>::not_working() 
                                  ^~~~~~
prog.cc:14:43: error: candidate is: template<class T> template<int i> static Dummy<Foo<T>::bar<i>()> Foo<T>::not_working()
   static Dummy<Foo<T>::template bar<i>()> not_working();
                                           ^~~~~~~~~~~

定义的返回类型为Dummy&lt;bar&lt;i&gt;()&gt;,但候选声明的返回类型为Dummy&lt;Foo&lt;T&gt;::bar&lt;i&gt;()&gt;。具体来说,bar&lt;i&gt; 上的 Foo&lt;T&gt;:: 资格已丢失。

also_working 的定义更改为返回类型Dummy&lt;Foo&lt;T&gt;::template bar&lt;2&gt;()&gt;,我们会得到有用的并行错误:

prog.cc:23:6: error: prototype for 'Dummy<Foo<T>::bar<2>()> Foo<T>::also_working()' does not match any in class 'Foo<T>'
 auto Foo<T>::also_working() -> Dummy<Foo<T>::template bar<2>()> 
      ^~~~~~
prog.cc:11:15: error: candidate is: template<class T> template<int i> static Dummy<Foo<T>::bar<i>()> Foo<T>::also_working()
   static auto also_working() -> Dummy<Foo<T>::template bar<i>()>;
               ^~~~~~~~~~~~

这里定义返回类型为Dummy&lt;Foo&lt;T&gt;::bar&lt;2&gt;()&gt;(如所写),候选声明的返回类型为Dummy&lt;Foo&lt;T&gt;::bar&lt;i&gt;()&gt;

显然,Foo&lt;T&gt;::bar&lt;i&gt;bar&lt;i&gt; 不同 即使在 Foo&lt;T&gt; 的上下文中,因为从 also_working 的返回类型的声明或定义中删除了 Foo&lt;T&gt;::template使它停止工作。 (把两者都拿出来让你回来working。)

我尝试将not_working 的声明更改为:

  template<int i>
  static Dummy<bar<i>()> not_working();

现在 gcc 抱怨:

prog.cc:28:34: error: prototype for 'Dummy<bar<i>()> Foo<T>::not_working()' does not match any in class 'Foo<T>'
 Dummy<Foo<T>::template bar<i>()> Foo<T>::not_working() 
                                  ^~~~~~
prog.cc:14:26: error: candidate is: template<class T> template<int i> static Dummy<bar<i>()> Foo<T>::not_working()
   static Dummy<bar<i>()> not_working();
                          ^~~~~~~~~~~

这显然是荒谬的,因为一旦编译器完成了它,我们就有了逐字符可比的声明和定义Dummy&lt;bar&lt;i&gt;()&gt; Foo&lt;T&gt;::not_working()

【讨论】:

感谢您与 GCC 进行较量,但恕我直言,特定编译器未能将定义与声明匹配这一事实并不能回答问题。我正在寻找所涉及的特定规则,告诉我定义是否应该根据标准与声明匹配,而不管使用的编译器如何。重新阅读我的问题,我发现它显然太模糊了:我会更新它以更具体。对不起,如果我的含糊不清误导了你。 嗯,您已经在 cmets 中得到 那个 的答案,规范中的顺序目前含糊不清,CWG 问题 2 旨在解决这个问题,但还没有已措辞并提交。我试图回答的是为什么它不起作用。碰巧 gcc 为我们提供了它试图匹配的线索,而 clang 只是说“不匹配”。

以上是关于使用签名中的另一个成员模板函数定义行外成员模板函数的主要内容,如果未能解决你的问题,请参考以下文章

模板实现顺序表

C++模板类中的成员函数以及模板函数在类外定义的方式

C++ Primer 5th笔记(chap 16 模板和泛型编程) 类模板的成员函数

模板初步——定义模板

成员模板函数

成员函数指针值上的 Consexpr - 未定义的行为?