使用C ++中的模板混淆函数查找

时间:2009-09-08 21:37:11

标签: c++ templates lookup

从以下开始(使用gcc version 4.0.1):

namespace name {
   template <typename T>
   void foo(const T& t) {
      bar(t);
   }

   template <typename T>
   void bar(const T& t) {
      baz(t);
   }

   void baz(int) {
      std::cout << "baz(int)\n";
   }
}

如果我添加(在全局命名空间中)

struct test {};
void bar(const test&) {
   std::cout << "bar(const test&)\n";
}
然后,正如我所料,

name::foo(test()); // produces "bar(const test&)"

但如果我只是添加

void bar(const double&) {
   std::cout << "bar(const double&)\n";
}

似乎无法找到这种重载:

name::foo(5.0) // produces "baz(int)"

更重要的是,

typedef std::vector<int> Vec;
void bar(const Vec&) {
   std::cout << "bar(const Vec&)\n";
}

也没有出现,所以

name::foo(Vec());

给出编译器错误

error: cannot convert ‘const std::vector<int, std::allocator<int> >’ to ‘int’ for argument ‘1’ to ‘void name::baz(int)’

这是查找应该如何工作的吗? (注意:如果删除命名空间name,那么一切都按预期工作。)

如何修改此示例以便考虑bar的任何重载? (我认为重载应该在模板之前被认为是?)

6 个答案:

答案 0 :(得分:8)

我假设您也将double版本添加到全局命名空间,并在定义完所有内容后从main调用foo。所以这基本上是两阶段名称查找。查找依赖的非限定函数名,因为调用中的参数依赖于(在其类型上)两个阶段。

第一阶段在定义上下文中执行非限定和参数依赖的查找。然后它冻结结果,并使用实例化上下文(实例化时声明的总和)执行第二个参数依赖查找不再进行非限定查询。因此,对于您的示例,它意味着:

  • bar(t)内的调用foo<test>在实例化上下文中使用参数相关查找查找bar(它使用非限定查找找不到它,因为{{1} }将foo声明为条形模板。根据您是在above模板之前还是之后定义全局bar,它将在第一阶段中找到使用参数相关查找的全局foo声明(它在{{1中定义)命名空间)。然后main中的调用将实例化bar,并且在此阶段可能会找到test(如果您在声明模板后声明了它)。

  • foo<test>内的调用bar不执行与参数相关的查找(或者更确切地说,查找的结果是空的声明集),因为bar(t)是一个基本类型。因此,定义上下文中的非限定查找也不会发现任何内容,因为匹配的foo<int>模板被声明为int bar模板。电话会议形成不良,标准会在after

    处说明这种情况
      

    如果呼叫形成不良[...]则程序会有不确定的行为。

    你应该认为这是“我做了一件肮脏的事情而编译器选择不打我”的情况,我认为:)

  • foo内的来电14.6.4.2/1会再次进行查询,并会在bar(t)中查找条形码(因为这是定义foo<Vec>的地方)。它在定义上下文中都没有找到std::。所以它决定再次使用未定义的行为,并使用std::vector模板,它本身再次通过使用后面声明的bar来执行未定义的行为,并且ADL和非限定查找都找不到它从定义上下文。

    如果向量是bar,那么baz的查找也将在全局范围内完成,因为依赖于参数的查找不仅会直接使用参数类型,还会使用模板的类型它们中的参数,如果有的话。


如果你使用GCC,那么就不要完全依赖它的行为。在下面的代码中,它声称调用是不明确的,尽管代码完全正常 - vector<test>中的bar不应该是候选者。

f

如果您想针对一致性测试您的代码段,请最好使用严格设置的comeau online compiler

答案 1 :(得分:2)

我可以确认您在我的系统上看到的行为,我认为这是正确的。

看起来重载解析只是查看它的参数的名称空间,因此bar的版本使test起作用,因为test在全局命名空间中,因此编译器在那里检查bar的版本,正如你正确指出的那样,它优先于模板版本。

对于Vec版本,重要命名空间为std。如果您在bar中放置了std版本,则会发现它会将其选中。

double版本不起作用,因为全局命名空间不用于查找,因为double是内置类型,并且不以任何方式与全局命名空间特别关联。

答案 2 :(得分:2)

在“c ++ koenig lookup”

上做谷歌

这应该为您提供有关模板查找规则的足够信息。

Herb Sutter有一篇关于这个主题的好文章:
http://www.gotw.ca/gotw/030.htm

答案 3 :(得分:0)

查找名称的规则是,如果名称不合格,则参数的命名空间将用于搜索该函数。

name::foo(test());有效,因为在foo中您基本上调用了bar(test());,并且测试的命名空间用于搜索栏。在这种情况下,全局命名空间。

name::foo(Vec());这不会起作用,因为Vec是一个typedef而不是类或结构。

请参阅函数名称查找规则的C ++标准。

答案 4 :(得分:-1)

以下程序适用于gcc 4.3和gcc 4.1(我手边只有两个编译器:

#include <iostream>
#include <vector>

namespace name {
   template <typename T>
   void foo(const T& t) {
      bar(t);
   }

   template <typename T>
   void bar(const T& t) {
      baz(t);
   }

   void baz(int) {
      std::cout << "baz(int)\n";
   }

   struct test {};
    void bar(const test&) {
       std::cout << "bar(const test&)\n";
    }

    void bar(const double&) {
       std::cout << "bar(const double&)\n";
    }

    typedef std::vector<int> Vec;
    void bar(const Vec&) {
       std::cout << "bar(const Vec&)\n";
    }
}

int main()
{
    name::foo(name::test());
    name::foo(5.0);
    name::foo(name::Vec());
}

产:

  

bar(const test&amp;)
  bar(const double&amp;)
  bar(const Vec&amp;)

您使用的是哪种编译器?

答案 5 :(得分:-1)

以下代码使用VS 2005 Professional Edition编译好:

#include <iostream>
#include <vector>

using std::cout;


typedef std::vector<int> Vec;

namespace name {
    template <typename T>
    void foo(const T& t) {
        bar(t);
    }

    template <typename T>
    void bar(const T& t) {
        baz(t);
    }

    void baz(int) {
        std::cout << "baz(int)\n";
    }   

    void bar(const Vec&) {
        std::cout << "bar(const Vec&)\n";
    }
}


int main()
{
    name::foo(Vec());
    return 0;
}

请发布原始代码,以便我们找出错误。