命名空间中的重载解析

5
我正在尝试在命名空间内调用一个重载函数,但有些困难。
示例1: 没有命名空间
class C {};

inline void overloaded(int) {}

template<typename T> void try_it(T value) {
  overloaded(value);
}

inline void overloaded(C) {}


int main()
{
  try_it(1);
  C c;
  try_it(c);
  return 0;
}

示例2:在模板之前定义所有重载

class C {};

namespace n {
  inline void overloaded(int) {}
  inline void overloaded(C) {}
}

template<typename T> void try_it(T value) {
  n::overloaded(value);
}

int main()
{
  try_it(1);
  C c;
  try_it(c);
  return 0;
}

错误示例3: 模板后面的一些重载

class C {};

namespace n {
  inline void overloaded(int) {}
}

template<typename T> void try_it(T value) {
  n::overloaded(value);
}

namespace n {
  inline void overloaded(C) {}
}

int main()
{
  try_it(1);
  C c;
  try_it(c); // /tmp/test.cpp: In function ‘void try_it(T) [with T = C]’:
             // /tmp/test.cpp:19:15:   instantiated from here
             // /tmp/test.cpp:8:7: error: cannot convert ‘C’ to ‘int’ for argument ‘1’ to ‘void n::overloaded(int)’

  return 0;
}

为什么会出现这种情况?我需要做什么才能在模板函数之后声明或定义重载函数?

如果您在模板之前声明它们,然后在实现时再进行定义,会怎样呢? - crush
与第3种情况相同的错误 - 请注意,第二个声明仍然在模板之后。由于代码组织问题,我不希望它出现在模板之前。 - Dark Falcon
你在这样做的时候移除了 inline 吗? - crush
是的,我移除了 inline - Dark Falcon
你的问题不是重载决议,而是名称查找(第二个重载未找到)。请考虑将此添加到问题标题和标签中。 - dyp
2个回答

3

这是一个依赖名称查找的问题。

在表达式 overloaded(value); 中,根据 [temp.dep]/1,名称 overloaded 是依赖的。

据我所知,在表达式 n::overloaded(value) 中,id-expression n::overloaded 中的名称 overloaded 不是依赖的。


依赖名称查找非常特殊,[temp.dep.res]/1

在解析依赖名称时,考虑以下来源的名称:

  • 在模板定义点可见的声明。
  • 函数参数类型相关联的命名空间中的声明,包括实例化上下文和定义上下文中的声明。

(对于函数模板,有一个实例化点在文件末尾,因此可以找到所有相关联命名空间的声明。)

对于非依赖名称,应用正常的查找规则 (从定义上下文查找)。

因此,要查找在模板定义后声明的名称,它们必须是依赖的,并且通过 ADL 找到。


一个简单的解决方法是给函数 overloaded 引入另一个参数,或将参数包装起来,使得该函数的一个参数与命名空间 n 相关联:

#include <iostream>

class C {};

namespace n {
  struct ADL_helper {};
  inline void overloaded(int, ADL_helper = {})
  { std::cout << "n::overloaded(int,..)" << std::endl; }
}

template<typename T> void try_it(T value) {
  overloaded(value, n::ADL_helper{});
}

namespace n {
  inline void overloaded(C, ADL_helper = {})
  { std::cout << "n::overloaded(C,..)" << std::endl; }
}

int main()
{
  try_it(1);
  C c;
  try_it(c);
}

另外一个选择是:

namespace n {
  template < typename T >
  struct wrapper { T elem; };

  inline void overloaded(wrapper<int>)
  { std::cout << "n::overloaded(wrapper<int>)" << std::endl; }
}

template<typename T> void try_it(T value) {
  overloaded(n::wrapper<T>{value});
}

namespace n {
  inline void overloaded(wrapper<C>)
  { std::cout << "n::overloaded(wrapper<C>)" << std::endl; }
}

谢谢,现在我理解得更清楚了。我选择使用命名空间外的函数是因为在我的情况下它会更易于维护。 - Dark Falcon

1

由于我最初错误地认为调用是一个非依赖名称,因此对原始版本进行了大量编辑。

好的,让我们来分解一下。

第二个示例有效,因为即使在非模板情况下,您也希望在所有重载声明之前使用正常工作。

第一个版本有效,因为根据14.6.4.2/1:

对于依赖于模板参数的函数调用,如果函数名是未限定的标识符但不是模板标识符,则使用通常的查找规则(3.4.1、3.4.2)找到候选函数,除此之外:

— 对于使用未限定名称查找(3.4.1)的查找部分,仅从模板定义上下文中具有外部链接的函数声明被找到。

— 对于使用相关命名空间(3.4.2)的查找部分,仅在模板定义上下文或模板实例化上下文中找到具有外部链接的函数声明。

我们特别关注第一部分如果函数名称是未限定的标识符和第二个项目在模板定义上下文或模板实例化上下文中找到。因此,我们了解到如果名称未限定,则在实例化点可见的名称将添加到候选集中。
同样,在第三种情况下,您的名称是完全限定的,因此抑制使用在实例化点可见的候选项,而是仅回退到定义点处的候选项。
如果您将c移入n,在try函数中添加using namespace n;,并从函数调用中删除n::,则会发现ADL再次选择重载,并且一切都很愉快。

@DarkFalcon 因为在这种情况下,全局命名空间与类 C 相关联。 - dyp
жңүи¶Јзҡ„жҳҜпјҢеңЁC++03е’ҢC++11д№Ӣй—ҙеҸ‘з”ҹдәҶеҸҳеҢ–пјҲ14.6.4.2/1пјүпјҢзңҒз•ҘдәҶвҖңеӨ–йғЁй“ҫжҺҘвҖқйғЁеҲҶпјӣиҝҷеҸҜиғҪдҪҝе…¶дёҺ[temp.dep.res]/1йҮҚеӨҚгҖӮ - dyp

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