模板元编程:检查稍后定义的函数是否存在

3
我正在解决一个经典问题:检查某个命名空间中是否存在自由函数。这个问题已经被讨论过了,例如在这里
然而,有一个小小的变化:函数的定义可能出现在检查类之后。以下是一个例子。
struct Yes {};
struct No {};
struct YesButLater {};

void f(Yes) {}

template<typename T, typename Enable = void>
struct HasF : public std::false_type {};

template<typename T>
struct HasF<T, decltype(void( ::f(T()) ))> : public std::true_type {};

void f(YesButLater) {}

int main() {
    cout << HasF<Yes>::value << endl; // 1
    cout << HasF<No>::value << endl; // 0
    cout << HasF<YesButLater>::value << endl; // 0, expected 1
}

f(YesButLater) 声明在 HasF 辅助类之后,尽管我在定义 f(YesButLater) 后实例化模板,但辅助程序并未注意到它。

那么,问题1:我该如何解决这个问题?

现在再来一个更有趣的例子。

template<typename T>
struct HasF<T, decltype(void( f(T()) ))> : public std::true_type {};

void f(YesButLater) {}
void f(std::string) {}

int main() {
    cout << HasF<YesButLater>::value << endl; // 1 (but what's the difference?)
    cout << HasF<std::string>::value << endl; // 0, expected 1
}

请注意,我从decltype(...)表达式中删除了::。现在由于某种原因,f(YesButLater)HasF注意到了,但f(std::string)仍然是模糊的。
问题2:为什么我们在这个例子中观察到::f(T())f(T())有不同的行为?此外,YesButLaterstd::string之间有什么区别?
我认为这里有一些命名空间查找的技巧,但我无法理解。

我的猜测是由于adl规则所致... - W.F.
@W.F. 是的,可能是这样,虽然我仍然不明白为什么::很重要,因为我在示例中总是使用全局命名空间。 - Ivan Smirnov
1个回答

3

看起来我已经搞清楚了发生了什么。

当我写::f(...)时,名称f正在进行限定名称查找。这种查找只遇到在调用点之前可用的声明的名称。现在很明显为什么第一个版本不能找到f(YesButLater):它的声明是后面才出现的。

当我写f(...)时,发生非限定名称查找。同样,在调用点之前未声明任何名称。这里参数依赖查找出现了。它在T所属的整个命名空间中搜索f(T)。对于f(YesButLater),此命名空间为全局,因此找到该函数。对于f(std::string),ADL尝试搜索std::f(std::string),当然失败了。

以下是两个示例以说明该情况。

namespace foo {
    class C {};
}

template<typename T>
void call() {
    f(T());
}

namespace foo {
    void f(C) {}
}

int main() {
    call<foo::C>();
}

在这里,使用ADL搜索f(T()),尽管它的声明在call()之后。如果我们修改call()函数…

template<typename T>
void call() {
    foo::f(T());
}

这会导致编译错误,因为foo::f(T)执行的是限定查找,并且无法找到所需的函数,因为此时没有声明可用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接