【问题标题】:Overload Resolution in a Namespace命名空间中的重载解析
【发布时间】:2013-09-03 18:12:14
【问题描述】:

我正在尝试在命名空间内调用重载函数,但有点挣扎。

工作示例 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;
}

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

【问题讨论】:

  • 如果您在模板之前声明它们并在之后实现它们会怎样?
  • 与案例 #3 相同的错误 - 请注意,第二个的声明仍在模板之后。由于代码组织问题,我不想在模板之前使用它。
  • 你删除了inline吗?
  • 是的,我删除了inline
  • 你的问题不是重载解析,而是名称查找(没有找到第二个重载)。考虑将其更改/添加到问题标题和标签中。

标签: c++ overload-resolution


【解决方案1】:

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

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

据我所知,在表达式 n::overloaded(value) 中,名称 overloaded(在 id-表达式 n::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; }
}

【讨论】:

  • 谢谢,现在更有意义了。我选择在命名空间之外使用函数,只是因为它在我的情况下更易于维护。
【解决方案2】:

原始版本的巨大编辑,我错误地认为调用是一个非依赖名称。

好的,让我们试着分解一下。

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

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

对于依赖于模板参数的函数调用,如果 函数名是一个非限定 ID 但不是模板 ID,候选 使用通常的查找规则(3.4.1、3.4.2)找到函数,除了 那:

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

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

我们对第一部分 if the function name is an unqualified-id 和第二部分 found in either the template definition context or the template instantiation context are found. 特别感兴趣,因此我们了解到,如果名称不合格,则会将在实例化时可见的名称添加到候选集中。

同样在第三种情况下,您的名称是完全限定的,因此禁止使用在实例化时可见的候选者,而仅在定义时使用候选者。 p>

如果您要将c 移动到n,将using namespace n; 添加到try 函数中,然后从函数调用中删除n::,您会发现ADL 将再次承担过载和都会很开心。

【讨论】:

  • @DarkFalcon 因为在这种情况下全局命名空间与C 类相关联。
  • 有趣的是,这里的 C++03 和 C++11 (14.6.4.2/1) 之间发生了变化,省略了“外部链接”部分;这可能会使 [temp.dep.res]/1 变得多余
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多