【问题标题】:Argument-dependent lookup within template模板中的参数相关查找
【发布时间】:2013-04-18 13:57:19
【问题描述】:

我对模板上下文中的函数名查找感到困惑。我知道编译器会延迟模板化代码中依赖于参数的标识符查找,直到模板被实例化。这意味着您有时可能会遇到语法错误或在模板代码中调用不存在的函数,除非您实际实例化模板,否则编译器不会抱怨。

但是,我发现不同编译器之间存在差异,我有兴趣了解标准本身的要求。

考虑以下代码:

#include <iostream>

class Foo
{
    public:

    template <class T>
    void bar(T v)
    {
        do_something(v);
    }
};

void do_something(std::string s)
{
    std::cout << "do_something(std::string)" << std::endl;
}

void do_something(int x)
{
    std::cout << "do_something(int)" << std::endl;
}

int main()
{
    Foo f;
    f.bar("abc");
    f.bar(123);
}

请注意,模板成员函数Foo::bar 调用了一个名为do_something不依赖于参数的全局函数,它甚至还没有被声明。 em>

然而,GCC 4.6.3 将愉快地编译上述程序。运行时,输出为:

do_something(std::string)
do_something(int)

Here's an ideone link.

因此,编译器似乎将标识符查找延迟到模板实例化之后,此时它能够找到do_something

相比之下,GCC 4.7.2 将编译上述程序。它会产生以下错误:

test.cc: In instantiation of ‘void Foo::bar(T) [with T = const char*]’:
test.cc:27:13:   required from here
test.cc:10:3: error: ‘do_something’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
test.cc:19:6: note: ‘void do_something(int)’ declared here, later in the translation unit

因此,GCC 4.7.2 知道 do_something 稍后被声明,但拒绝编译程序,因为 do_something 不依赖于参数。

所以,我假设 GCC 4.7.2 在这里可能是正确的,而 GCC 4.6.3 是不正确的。所以大概,我需要在定义Foo::bar 之前声明do_something。这个问题是假设我想允许我的班级Foo 的用户通过实现他们自己的do_something 重载来扩展Foo::bar 的行为。我需要写一些类似的东西:

#include <iostream>

template <class T>
void do_something(T v)
{
    std::cout << "do_something(T)" << std::endl;
}

class Foo
{
    public:

    template <class T>
    void bar(T v)
    {
        do_something(v);
    }
};

void do_something(int x)
{
    std::cout << "do_something(int)" << std::endl;
}

int main()
{
    Foo f;
    f.bar("abc");
    f.bar(123);
}

这里的问题是do_something 的重载在Foo::bar 中是不可见的,因此从未调用过。所以即使我调用do_something(int),它也会调用do_something(T),而不是int 的重载。因此,对于 GCC 4.6.3 和 GCC 4.7.2,上述程序输出:

do_something(T)
do_something(T)

那么这里有哪些解决方案?如何允许用户通过实现自己的 do_something 重载来扩展 Foo::bar

【问题讨论】:

  • 即使引入了转换率最低的“包罗万象”,问题仍然存在:ideone.com/OSGoqJ

标签: c++ templates argument-dependent-lookup


【解决方案1】:

就重载do_something 而言,您需要专门化您的原始模板:

template<>
void do_something<int>(int x) {
    std::cout << "do_something(int)" << std::endl;
}

编辑: 作为@MatthieuM。指出,如果您还需要重载函数(并且在某些时候您可能需要,因为函数模板不能部分特化),函数模板特化会产生奇怪的结果。有关完整说明,请参阅 Matthieu 指向 Herb Sutter 文章 Why Not Specialize Function Templates? 的链接。

建议使用包装在结构中的静态函数,它允许部分特化并消除重载函数模板带来的名称解析问题。

template<typename T>
struct DoSomething {
    static void do_something(T v) {
        std::cout << "do_something(T)" << std::endl;
    }
};

struct Foo
{
    template <class T>
    void bar(T v) {
        DoSomething<T>::do_something(v);
    }
};

// Now you can specialize safely
template<>
struct DoSomething<int> {
    static void do_something(int v) {
        std::cout << "do_something(int)" << std::endl;
    }
};

【讨论】:

  • 它会在here工作,但总的来说这是一个糟糕的主意。名称查找和模板专业化以非常奇怪的方式交互。
  • @MatthieuM。需要详细说明吗?据我了解模板专业化(我承认这可能不是一个完整的理解),专业化是在实例化点选择的。我意识到这可能过于简单,但我从来没有被这个咬过(即使我经常使用它)所以我从来没有深入挖掘。 ;)
  • Herb Sutters 的Why Not Specialize Function Templates? 最好地说明了这一点。关键是首先查找“真正的”函数模板,并选择最佳匹配,然后编译器才会查找该基本函数模板的特殊化需要考虑。因此,要知道是否选择了您的专业,您需要知道它与哪个基本功能模板相关......这很奇怪,真的,真的,很奇怪。
  • @MatthieuM。啊谢谢我现在明白了,这确实令人困惑(我也明白为什么我从来没有遇到过任何问题:我总是使用包装在结构中的静态函数来允许部分专业化——有趣的是,这正是 H.Sutter 在他的建议道德#2 点)。我会相应地编辑我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多