如何让我的模板化函数看到后面定义的其他全局方法?

Dan*_*bek 6 c++ gcc templates

(Oktalist在下面给出了一个很好的答案,查看它下面的评论,以帮助演示我们讨论的所有内容,我在问题的底部添加了一个完整的编译解决方案,演示了所讨论的所有内容.)

我有一组命名空间全局方法和模板化方法,如下所示:

namespace PrettyPrint
{
    String to_string(bool val);
    String to_string(char val);
    String to_string(int val);
    String to_string(uint val);
    // ETC...

    template <typename T> String to_string(const T* val)
    {
        if (! val) return U("NULL");
        return String().copy_formatted("(%p)-> %S", (const void*)val, to_string(*val).uchars());
    }

    // ... and more templates to help with containers and such
}
Run Code Online (Sandbox Code Playgroud)

"String"类型不是C++字符串,它是从IBM的ICU库派生的特殊类,但与此问题并不真正相关.重点是,我有一堆称为to_string的命名空间全局方法,还有一些覆盖它们的模板化函数.到目前为止,这么好,一切都很好.但是,现在我有另一个标题,我有类似下面的定义:

namespace User 
{
    struct Service {
        int code;
        String name;
    }
    //...
}
namespace PrettyPrint
{
    String to_string(const User::Service& val) { return val.name; }
}
Run Code Online (Sandbox Code Playgroud)

所以,现在我已经在其他地方定义了一些其他类型,我还在PrettyPrint命名空间中定义了另一个to_string覆盖,以指示如何将我的新类型转换为String.将两个标题放入文件中,如下所示:

#include <the to_string and templates header>
#include <the User::Service header>

main() {
    User::Service s = {1, U("foo")};
    User::Service *p = &s;
    PrettyPrint::to_string(s);
    PrettyPrint::to_string(p);
}
Run Code Online (Sandbox Code Playgroud)

(是的,to_string方法实际上应该在某个地方返回一个值,而不是点.)重点是第二个调用给出了编译器错误(gcc,btw),说明在模板化的to_string方法中没有匹配的函数用于调用'to_string(const User :: Service&)',当然与我定义和包含的方法完全匹配.如果我反转#include顺序它就可以了.

所以,我猜测模板只关注前面定义的方法.有没有解决这个问题?鉴于我的项目范围和复杂的#include的数量,简单地说"始终确保它们按正确的顺序排列"并不是一个易处理的解决方案,并且会在代码中引入太多复杂的脆弱性.基本的to_string定义是那些倾向于被包含在很多地方的文件之一,所以确保碰巧包含to_string覆盖的任何其他随机类型定义都是不可行的.

我在某些地方工作的另一个解决方案是我在基本文件中定义了一个Printable接口:

namespace PrettyPrint
{
    class Printable {
    public:
        virtual String pretty_print_to_string() const = 0;
    }

    String to_string(const Printable& obj) { return obj.pretty_print_to_string(); }
}
Run Code Online (Sandbox Code Playgroud)

该定义出现在同一文件中的模板方法之前.所以,这对于我可以简单地添加该接口并实现它的类非常有用.没有任何好的解决方案,我将简单地尝试总是使用它,但有些地方不方便,我也只是想了解是否有任何方法可以让方法重载解决方案工作不依赖于#include命令工作.

无论如何,你们都对这些选择有什么看法?是否有一种我没有想到的方法可以很好地工作?

解决方案的灵感源自Oktalist的回答

这段代码实际上是编译好的,所以你可以把它复制下来玩它,我想我已经捕获了所有相关的用例以及哪些有效,什么无效以及为什么.

#include <iostream>

using namespace std;

namespace PrettyPrint
{
    void sample(int val) { cout << "PrettyPrint::sample(int)\n"; }
    void sample(bool val) { cout << "PrettyPrint::sample(bool)\n"; }
    template<typename T> void sample(T* val) { cout << "PrettyPrint::sample(pointer); -> "; sample(*val); }
}

namespace User
{
    struct Foo {
        int i;
        bool b;
    };

    void sample(const Foo& val) {
        //below doesn't work un-qualified, tries to convert the int (val.i) into a Foo to make a recursive call.
        //meaning, it matches the User namespace version first
        //sample(val.i); doesn't work, tries to call User::sample(const Foo&)

        cout << "User::sample(const Foo&); -> {\n";
        cout << '\t'; PrettyPrint::sample(val.i); //now it works
        cout << '\t'; PrettyPrint::sample(val.b);
        cout << "}\n";
    }
}

namespace Other
{
    void test(User::Foo* fubar) {
        cout << "In Other::test(User::Foo*):\n";
        //PrettyPrint::sample(*fubar); //doesn't work, can't find sample(const User::Foo&) in PrettyPrint
        PrettyPrint::sample(fubar); //works, by argument-dependent lookup (ADL) from the template call
        sample(*fubar); //works, finds the method by ADL
        //sample(fubar); //doesn't work, only sees User::sample() and can't instantiate a Foo& from a Foo*
    }

    void test2(User::Foo* happy) {
        using PrettyPrint::sample; //now both work! this is the way to do it.
        cout << "In Other::test2(User::Foo*):\n";
        sample(*happy);
        sample(happy);
    }
}

int main() {
    int i=0, *p = &i;
    bool b=false;
    User::Foo f = {1, true}, *pf = &f;

    //sample(i);  <-- doesn't work, PrettyPrint namespace is not visible here, nor is User for that matter.
    PrettyPrint::sample(i); //now it works
    //PrettyPrint::sample(f); //doesn't work, forces search in PrettyPrint only, doesn't see User override.

    using namespace PrettyPrint;  // now they all work.
    sample(p);
    sample(b);

    sample(f);
    sample(pf);

    Other::test(pf);
    Other::test2(pf);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这导致以下输出:

PrettyPrint::sample(int)
PrettyPrint::sample(pointer); -> PrettyPrint::sample(int)
PrettyPrint::sample(bool)
User::sample(const Foo&); -> {
    PrettyPrint::sample(int)
    PrettyPrint::sample(bool)
}
PrettyPrint::sample(pointer); -> User::sample(const Foo&); -> {
    PrettyPrint::sample(int)
    PrettyPrint::sample(bool)
}
In Other::test(User::Foo*):
PrettyPrint::sample(pointer); -> User::sample(const Foo&); -> {
    PrettyPrint::sample(int)
    PrettyPrint::sample(bool)
}
User::sample(const Foo&); -> {
    PrettyPrint::sample(int)
    PrettyPrint::sample(bool)
}
In Other::test2(User::Foo*):
User::sample(const Foo&); -> {
    PrettyPrint::sample(int)
    PrettyPrint::sample(bool)
}
PrettyPrint::sample(pointer); -> User::sample(const Foo&); -> {
    PrettyPrint::sample(int)
    PrettyPrint::sample(bool)
}
Run Code Online (Sandbox Code Playgroud)

Okt*_*ist 4

在两阶段查找的第一阶段,当定义模板时,非限定查找会在模板的直接封闭命名空间中查找依赖名称和非依赖名称,并仅查找to_string出现在模板定义之前的那些重载。

在两阶段查找的第二阶段,当实例化模板时,依赖于参数的查找会在与作为参数传递给命名函数的任何类类型关联的命名空间中查找依赖名称。但由于您的to_string(const User::Service&)重载位于PrettyPrint名称空间中,因此依赖于参数的查找将无法找到它。

to_string(const User::Service&)重载移到User命名空间中以利用依赖于参数的查找,这将找到在模板实例化点声明的任何重载,包括在模板定义点之后声明的任何重载。

另请参阅http://clang.llvm.org/compatibility.html#dep_lookup