模板函数重载未按预期调用

时间:2011-01-25 08:39:16

标签: c++ templates overload-resolution

我的情况如下:

我有一个模板包装器,可以处理值和对象可以为空的情况,而无需手动处理指针甚至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<T>中。

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

更新:澄清一下 - 重载的Add函数不会被导出,只能在DLL内部使用。换句话说,遇到此问题的外部程序甚至不包括具有模板重载功能的头部。

2 个答案:

答案 0 :(得分:0)

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

隐含的转换是危险的,经常会咬你。它可能只是包含您的标题或您正在使用的命名空间。

我会做以下事情:

  • 使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&amp;使用命名函数进行转换。对于非常量使用ref(),对const使用cref()。

我也会用

完成课程
  • 赋值运算符(3规则需要)
  • 操作符 - &GT;传播constness时会出现两次重载。

如果你计划将它用于C ++ 0x,还要使用r值复制和赋值,这在这种情况下很有用。

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

答案 1 :(得分:0)

好吧,因为到目前为止还没有找到真正的答案,我已经做了一个解决方法。基本上,我将前面提到的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;
    }

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