【问题标题】:Template function overload not called as expected模板函数重载未按预期调用
【发布时间】:2011-01-25 08:39:16
【问题描述】:

我的情况如下:

我有一个模板包装器,它可以处理值和对象可以为空的情况,而无需手动处理指针甚至new。这基本上归结为:

struct null_t
{
  // just a dummy
};
static const null_t null;

template<class T> class nullable
{
public:
  nullable()
    : _t(new T())
  {}

  nullable(const nullable<T>& source)
    : _t(source == null ? 0 : new T(*source._t))
  {}

  nullable(const null_t& null)
    : _t(0)
  {}

  nullable(const T& t)
    : _t(new T(t))
  {}

  ~nullable()
  {
    delete _t;
  }

  /* comparison and assignment operators */

  const T& operator*() const
  {
    assert(_t != 0);
    return *_t;
  }

  operator T&()
  {
    assert(_t != 0);
    return *_t;
  }

  operator const T&() const
  {
    assert(_t != 0);
    return *_t;
  }
private:
  T* _t;
};

现在使用比较运算符,我可以检查 null_t 虚拟对象,以便在实际尝试检索值或将其传递给需要该值并执行自动转换的函数之前查看它是否设置为 null .

这门课已经让我受益良久,直到我偶然发现了一个问题。我有一个包含一些结构的数据类,这些结构将全部输出到文件(在本例中为 XML)。

所以我有这样的功能

xml_iterator Add(xml_iterator parent, const char* name,
                 const MyDataStruct1& value);

xml_iterator Add(xml_iterator parent, const char* name,
                 const MyDataStruct2& value);

每个都用适当的数据填充 XML-DOM。这也可以正常工作。

然而,现在,其中一些结构是可选的,在代码中将被声明为

nullable<MyDataStruct3> SomeOptionalData;

为了处理这种情况,我做了一个模板重载:

template<class T>
xml_iterator Add(xml_iterator parent, const char* name,
                 const nullable<T>& value)
{
  if (value != null)  return Add(parent, name, *value);
  else                return parent;
}

在我的单元测试中,正如预期的那样,无论值或结构包含在 nullable&lt;T&gt; 中,编译器总是首选选择此模板函数。

但是,如果我使用上述数据类(在其自己的 DLL 中导出),由于某种原因,第一次应该调用最后一个模板函数,而不是从 nullable&lt;T&gt; 自动转换为相应的类型 @987654329 @ 完成,完全绕过了处理这种情况的函数。正如我上面所说的 - 所有单元测试都 100% 正常,测试和调用代码的可执行文件都是由 MSVC 2005 在调试模式下构建的 - 这个问题绝对不能归因于编译器的差异。

更新:澄清 - 重载的 Add 函数不会导出,仅在 DLL 内部使用。也就是说,遇到这个问题的外部程序甚至不包括带有模板重载函数的头部。

【问题讨论】:

  • 你在定义命名空间吗?您的定义位于哪个命名空间中?图书馆呢?
  • 您可能需要查看boost::optional,以获得强大而完整的解决方案。
  • @GMan:是的,这与nullable 所做的非常相似,但是boost::optiona 具有相同的自动转换行为,因此无法解决当前的问题。

标签: c++ templates overload-resolution


【解决方案1】:

编译器会在找到模板化版本之前主要选择完全匹配,但会选择模板化“完全匹配”而不是另一个适合的函数,例如,使用您类型的基类的函数。

隐式转换很危险,而且经常会咬你一口。这可能只是您包含标头或正在使用的命名空间的方式。

我会做以下事情:

  • 使 Nullable 的构造函数全部显式。您可以使用任何只接受一个参数的构造函数来执行此操作,或者可以用一个参数调用(即使还有更多具有默认值的构造函数)。

    template<class T> class nullable
    
    {
      public:
        nullable() 
           : _t(new T())
        {}
    
    
    explicit nullable(const nullable<T>& source)
       : _t(source == null ? 0 : new T(*source._t))
    {}
    
    explicit nullable(const null_t& null)
        : _t(0)
      {}
    
      explicit nullable(const T& t)
        : _t(new T(t))
      {}
    // rest
    };
    
  • 将运算符 T& 转换替换为命名函数。对非常量使用 ref(),对 const 使用 cref()。

我也会完成这门课

  • 赋值运算符(规则 3 需要)
  • operator-> 传播常量时的两个重载。

如果您打算将其用于 C++0x,还可以使用 r-value 复制和分配,这在这种情况下很有用。

顺便说一句,您确实知道您的深层副本不适用于基类,因为它们会切片。

【讨论】:

  • 是的,我剪掉了赋值、-&gt; 运算符等。而且这个类是用来保存一个具体类型的,基本上不是有一个变量int x = 42;,你也可以创建一个nullable&lt;int&gt; y = 42;。该模板并不意味着通过仅存储指向基类的指针来处理多态性。
【解决方案2】:

好吧,由于到目前为止还没有找到真正的答案,所以我做了一个解决方法。基本上,我将上述Add 函数放在单独的detail 命名空间中,并添加了两个模板包装函数:

    template<class T>
    xml_iterator Add(xml_iterator parent, const char* name,
                     const T& value)
    {
      return detail::Add(parent, name, value);
    }

    template<class T>
    xml_iterator Add(xml_iterator parent, const char* name,
                     const nullable<T>& value)
    {
      return value != null ? detail::Add(parent, name, *value) : parent;
    }

我发现这总是可以正确解析这两个函数中的一个,并且实际包含类型的函数将在其中的单独步骤中选择,如您所见。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    • 2019-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多