在C ++模板函数中,为什么依赖函数调用给出“未声明”错误?

时间:2010-10-16 23:11:04

标签: c++

在C ++模板函数foo()中,对:: bar(TT *)的调用在gcc 4.4.3下给出了以下错误:

g++ -o hello.o -c -g hello.cpp
hello.cpp: In function 'void foo(std::vector<TT*, std::allocator<TT*> >&)':
hello.cpp:8: error: '::bar' has not been declared

以下是有问题的代码:

// hello.cpp

#include <vector>

template<typename TT> void foo(std::vector<TT*> &vec)
{
    TT *tt;
    ::bar(tt);
    vec.push_back(tt);
}

class Blah
{
};

void bar(Blah *&)
{
}

int main(int argc, char *argv[])
{
    std::vector<Blah*> vec;
    foo(vec);

    return 0;
}

C ++区分依赖于模板参数(TT,此处)的符号和那些独立且可以立即评估的符号。

显然,编译器认为我的:: bar(TT *)调用是独立的,并尝试立即解决它。同样清楚的是,函数调用 依赖于TT,因为函数调用采用TT *类型的参数,因此编译器应该等到foo(vec)实例化来解析:: bar(TT * )。

这是一个gcc错误还是我遗漏了一些关于C ++模板的微妙内容?

编辑:这是一个稍微复杂的例子,有两个版本的:: bar()来澄清声明顺序不是我的问题。解析模板时,编译器有方式知道下面的main()是否要用TT = Blah或TT = Argh实例化模板函数。因此,编译器不应该在最早(如果有的话)第35行第28行之前给出错误。但是错误是针对第8行第16行给出的。

编辑#2:改进了这个例子。

编辑#3:对此示例添加了更正以使其按预期工作。 bar(tt)现在正确指向bar(Blah *)。基本原理如下。 (谢谢大家)。

// hello.cpp
#include <vector>

class XX {};
void bar(XX*) {}

class CC {
public:
    void bar();
    void bar(int *);
    void bar(float *);

    template<typename TT> static void foo(std::vector<TT*> &vec);
};

template<typename TT>
void CC::foo(std::vector<TT*> &vec) {
    using ::bar;
    TT *tt;
    bar(tt);
    vec.push_back(tt);
}

class Argh {};
void bar(Argh *&aa) { aa = new Argh; }

class Blah {};
void bar(Blah *&bb) { bb = new Blah; }

int main(int argc, char *argv[]) {
    std::vector<Blah*> vec;
    CC::foo(vec);
    return 0;
}

6 个答案:

答案 0 :(得分:5)

来自C ++规范的第14.7.2节:

  

表格形式:

    postfix-expression ( expression-listopt )
  

其中postfix-expression是unqualified-id但不是template-id,unqualified-id表示依赖   当且仅当表达式列表中的任何表达式是依赖于类型的表达式时才会命名(14.7.2.2)。

由于::b不是非限定ID,因此它不是从属名称。如果删除::,则它是一个不合格的名称,因此是一个从属名称。对于非依赖名称,查找发生在模板声明的位置,而不是实例化,此时没有bar的全局声明可见,因此您会收到错误。

答案 1 :(得分:5)

  

目前还没有人指出现行标准的任何部分说我不能。

C ++ 03不会使名称::bar依赖。依赖类型的类型名称和依赖于类型或值的依赖表达式的非类型名称的依赖关系。如果在依赖类型中查找名称,它将成为依赖于类型的id-expression(14.6.2.2/3 last bullet),并且其查找将延迟到实例化。名称::bar不是这样的依赖表达式。如果你打电话给bar(tt),那么C ++ 03的特殊规则14.2.6说

  

表格形式:

postfix-expression ( expression-listopt )
     

其中postfix-expression是标识符,当且仅当表达式列表中的任何表达式是依赖于类型的表达式时,标识符表示从属名称(14.6.2.2)。

因此,您需要删除::才能使其成为标识符,并使其符合此特殊规则。

  

我无法删除::的原因是在我的实际代码中,模板函数foo是类CC的成员函数,并且存在一系列重载的成员函数CC :: bar(。 ..),意思是我需要限定:: bar(TT *)以避免默认为CC :: bar(...)。这就是::存在的,如果标准说我不能使用::这里我很惊讶

解决问题的正确方法是在函数的本地范围内引入using声明。

namespace dummies { void f(); }
template<typename T>
struct S {
  void f();
  void g() { 
    using dummies::f; // without it, it won't work
    f(T()); // with ::f, it won't work
  }
};

struct A { };
void f(A) { } // <- will find this

int main() {
  S<A> a;
  a.g();
}

如果普通查找找到类成员函数,ADL将不会执行任何操作。因此,您引入了一个using声明,因此普通查找找不到类成员函数,并且ADL可以在实例化时推进声明可见。

  

但这似乎与你意见不一致:Stroustrup TC ++ PL Sp Ed,C.13.8.1节,依赖名称:“基本上,如果一个函数的名称显然依赖于在其论点或其正式参数“

Stroustrup的书也是为那些可能还不了解C ++的人编写的。它不会试图以100%的准确度覆盖所有规则,这对这些书来说是正常的。详细信息留给了ISO标准读者。

此外,函数的形式参数与函数调用是否依赖无关。在IS中,只有实际参数定义函数名的依赖关系。这在an old draft from 1996中有所不同,它具有隐式显式依赖关系的概念。隐式依赖被定义为

  

如果名称是函数,则名称隐式依赖于template-argument   在函数调用中使用的名称和函数调用将有一个不同的   如果是类型,模板或枚举器,则分辨率不高或无分辨率   程序中缺少模板参数中提到的内容。

     

[...]

     

[示例:一些依赖于模板参数类型T的调用是:

     
      
  1. 调用的函数有一个依赖于T的参数   类型扣除规则( temp.deduct )。例如,f(T),   f(数组)和f(常数T *)。

  2.   
  3. 实际参数的类型取决于T.例如,f(T(1)),   假设t具有类型T,则f(t),f(g(t))和f(&amp; t)。

  4.   

还给出了一个实际的例子

  

这种格式错误的模板实例化使用的功能不是   取决于模板参数:

template<class T> class Z {
public:
        void f() const
        {
                g(1); // g() not found in Z's context.
                      // Look again at point of instantiation
        }
};

void g(int);
void h(const Z<Horse>& x)
{
        x.f(); // error: g(int) called by g(1) does not depend
               // on template-argument ``Horse''
}
     

调用x.f()会产生专业化:

void Z<Horse>::f() { g(1); }
     

调用g(1)将调用g(int),但由于该调用不依赖   在模板参数Horse上,因为g(int)不在范围内   关于模板定义的要点,调用x.f()是错误的   形成。

     

另一方面:

void h(const Z<int>& y)
{
        y.f(); // fine: g(int) called by g(1) depends
               // on template-argument ``int''
}
     

这里,调用y.f()会产生专业化:

void Z<int>::f() { g(1); }
     

调用g(1)调用g(int),因为该调用取决于tem-   plate-argument int,即使g(int),调用y.f()也是可以接受的   在模板定义的范围内不在范围内。 ]

这些东西留给了历史,甚至它的最后痕迹都在慢慢消失,虽然没有被主动驱动(例如n3126在[temp.names] / p4中摆脱“明确依赖”作为副作用另一个变化,因为“明确依赖”和“隐含依赖”之间的区别在IS中从未存在过。

答案 2 :(得分:2)

这种丑陋适用于英特尔C ++ 11.0,也许可以说明编译器的观点:

#include <vector>
#include <iostream>

// *********************
// forward declare in the global namespace a family of functions named bar
// taking some argument whose type is still a matter of speculation
// at this point
template<class T>
void bar(T x);
// *********************

template<typename TT>
void foo(std::vector<TT*> &vec)
{
   TT *tt;
   ::bar(tt);
   vec.push_back(tt);
}

class Blah
{
  public:
};

void bar(Blah *x)
{
  // I like output in my examples so I added this
  std::cout << "Yoo hoo!" << std::endl;
}

// **********************
// Specialize bar<Blah*>
template<>
inline
void bar<Blah*>(Blah *x) { ::bar(x); }
// **********************

int main(int, char *)
{
  std::vector<Blah*> vec;
  foo(vec);

  return 0;
}

答案 3 :(得分:1)

这应该有效,你的问题是声明顺序:

// hello.cpp

#include <vector>


class Blah
{
public:

};

void bar(Blah *&)
{ }

template<typename TT> void foo(std::vector<TT*> &vec)
{
    TT *tt;
    ::bar(tt);
    vec.push_back(tt);
}


int main(int argc, char *argv[])
{
    std::vector<Blah*> vec;
    foo(vec);

    return 0;
}

答案 4 :(得分:1)

依赖名称仍需要函数声明。任何具有该名称的函数声明都可以,但必须有一些东西。编译器如何消除一个重载函数调用的歧义,例如拼写错误的仿函数对象?换句话说,找到该名称的某些函数,从而验证该名称上的重载是否可行,在限定(或非限定)名称查找中启动重载解析过程。

// hello.cpp

#include <vector>

void bar(); // Comeau bails without this.

template<typename TT> void foo(std::vector<TT*> &vec)
{
    TT *tt;
    ::bar(tt);
    vec.push_back(tt);
}

class Blah
{
};

void bar(Blah *&)
{
}

int main(int argc, char *argv[])
{
    std::vector<Blah*> vec;
    //foo(vec); - instanting it is certainly an error!

    return 0;
}

答案 5 :(得分:0)

您的代码在VS2005上运行正常。 所以它确实看起来像是gcc中的一个bug。 (我不知道这些规格是怎么说的,也许会有人来发布它。)

至于gcc,我还尝试在bar之前定义foo的不同重载,以便至少定义符号bar

void bar(float *) {
}

template<typename TT> void foo(std::vector<TT*> &vec)
{
    TT *tt;
    ::bar(tt);
    vec.push_back(tt);
}

void bar(int *) {
}

int main(int argc, char *argv[])
{
    std::vector<int*> vec;
    foo(vec);

    return 0;
}

但是gcc对int *重载仍然完全失明:

  

错误:无法将参数1的int *转换为float *为void bar(float *)

似乎gcc只会使用模板本身之前定义的那些函数。

但是,如果删除显式的::说明符并将其设为bar(tt),它似乎工作正常。克里斯多德的回答似乎令人满意,解释了为什么会这样。