如果一个函数类型只依赖于它自己的模板参数,它是否依赖于它?

时间:2014-12-26 23:35:30

标签: c++ templates language-lawyer dependent-name

我遇到了当前C ++编译器(clang / gcc)确定名称是否依赖的方式不一致。在以下示例中,A::f是相关的,但::f不是,在使用后者时会导致错误。

template<typename>
struct B
{
    typedef int Type;
};

template<typename U>
static U f(U u);

template<typename T>
struct A
{
    template<typename U>
    static U f(U u);

    typename B<decltype(f(0))>::Type m1; // typename required
    B<decltype(::f(0))>::Type m2; // typename not required
};

不一致的部分是A::f的声明不依赖于A的模板参数,这意味着似乎没有必要将其视为从属名称。

这种行为似乎包含在C ++ 11标准中的以下措辞中:

  

[temp.dep.expr] / 3

     

如果id-expression包含

,则它依赖于类型      
      
  • 通过名称查找与一个或多个使用依赖类型声明的声明相关联的标识符
  •   
     

[temp.dep.type] / 3

     

如果类型是

,则类型是依赖的      
      
  • 由任何依赖类型构建的复合类型
  •   

::f的声明显然不依赖,因为它的类型仅取决于它自己的模板参数。为什么A::f应该区别对待?

1 个答案:

答案 0 :(得分:3)

我认为基于标准,f实际上是非依赖的。

  

14.6.2.2依赖于类型的表达式[temp.dep.expr]

     

3 id-expression 如果包含

,则取决于类型      
      
  • 通过名称查找关联的标识符,其中一个或多个声明使用依赖类型声明,
  •   

这同样适用于全局模板函数,因为它对成员模板函数也是如此:完全没有。 U的返回类型取决于模板函数的定义,但对于调用者,f<int>的函数类型已从U(U)转换为int(int)。无论如何,它无法解释为什么编译器以不同的方式处理这两种情况,也无法解释为什么非模板成员函数也被视为依赖。

  
      
  • 依赖的 template-id
  •   
  • 指定依赖类型的 conversion-function-id ,或
  •   

这些不适用。 template-id 中必须始终存在<>,并且不会调用转换函数。

  
      
  • 嵌套名称说明符 qualified-id ,用于命名未知专业化的成员;
  •   

见下文。

  

或者它是否命名当前实例化的静态数据成员,该实例的类型为&#34;未知范围为T&#34的数组;某些T(14.5.1.3)。

这也不适用:没有涉及数组。

所以这取决于f是否是未知专业化的成员。但它不是:

  

14.6.2.1依赖类型[temp.dep.type]

     

5名称是未知专业化的成员,如果它是

     
      
  • qualified-id ,其中[...]。
  •   
  • qualified-id ,其中[...]。
  •   
  • id-expression 表示类成员访问表达式(5.2.5)中的成员,其中[...]。
  •   

这些不适用:f既不是合格的也不是类成员访问表达式的一部分。

由于f可以依赖的唯一方式是它是未知专业化的成员,并且它不是未知专业化的成员,f必须不依赖。

至于为什么编译器仍将它视为依赖,我没有答案。我的答案的某些部分是错误的,编译器有错误,或者编译器遵循不同版本的C ++标准。使用一个无论名称是否依赖的示例进行测试都会显示编译器处理的一些变化:

#include <cstdio>

void f(const char *s, ...) { std::printf("%s: non-dependent\n", s); }

struct S1 { };

template <typename T>
struct S2 {
  static S1 a;
  static S1 b() { return {}; }
  template <typename U>
  static U c() { return {}; }
  static void z() {
    f("S1()", S1()); // sanity check: clearly non-dependent
    f("T()", T()); // sanity check: clearly dependent
    f("a", a); // compiler agreement: non-dependent
    f("b()", b()); // compiler disagreement: dependent according to GCC 4.8, non-dependent according to clang
    f("c<T>()", c<T>()); // sanity check: clearly dependent
    f("c<S1>()", c<S1>()); // compiler agreement: dependent
    f("decltype(b())()", decltype(b())()); // compiler agreement: dependent
  }
};

void f(const char *s, S1) { std::printf("%s: dependent\n", s); }

// Just to show it's possible to specialize the members
// without specializing the full template.
template <>
S1 S2<S1>::b() { return {}; }
template <>
template <>
S1 S2<S1>::c<S1>() { return {}; }

int main() {
  S2<S1>::z();
}

b()decltype(b())()c<S1>()对clang的处理方式的这种差异对我来说尤其令人不安。它没有任何意义。他们显然都是同样依赖的。我可以从实现的角度理解,必须注意不要为成员函数生成代码,因为可能存在S2<S1>::bS2<S1>::c<S1>的特化,但这适用于所有,并且对返回类型没有影响。