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

发布于 2024-10-13 12:40:54 字数 2051 浏览 6 评论 0原文

我的情况如下:

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

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

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 中。

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

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

My situation is the following:

I have a template wrapper that handles the situation of values and object being nullable without having to manually handle pointer or even new. This basically boils down to this:

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;
};

Now with the comparison operators I can check against the null_t dummy in order to see whether it is set to null before actually trying to retrieve the value or pass it into a function that requires that value and would do the automatic conversion.

This class has served me well for quite some time, until I stumbled about an issue. I have a data class containing some structs which will all be outputted to a file (in this case XML).

So I have functions like these

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

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

which each fill an XML-DOM with the proper data. This also works correctly.

Now, however, some of these structs are optional, which in code would be declared as a

nullable<MyDataStruct3> SomeOptionalData;

And to handle this case, I made a template overload:

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;
}

In my unit tests the compiler, as expected, always preferred to choose this template function whereever a value or structure is wrapped in a nullable<T>.

If however I use the aforementioned data class (which is exported in its own DLL), for some reason the very first time that last template function should be called, instead an automatic conversion from nullable<T> to the respective type T is done, completely bypassing the function meant to handle this case. As I've said above - all unit tests went 100% fine, both the tests and the executable calling the code are being built by MSVC 2005 in debug mode - the issue can definitely not be attributed to compiler differences.

Update: To clarify - the overloaded Add functions are not exported and only used internally within the DLL. In other words, the external program which encounters this issue does not even include the head with the template overloaded function.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

恋你朝朝暮暮 2024-10-20 12:40:54

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

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

我会执行以下操作:

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

    模板类可为空
    
    {
      民众:
        可为空() 
           : _t(新T())
        {}
    
    
    显式可为空(const nullable& 源)
       : _t(源 == null ? 0 : 新 T(*source._t))
    {}
    
    显式可为空(const null_t& null)
        : _t(0)
      {}
    
      显式可为空(const T& t)
        : _t(新 T(t))
      {}
    // 休息
    };
    
  • 替换运算符T&与命名函数的转换。使用 ref() 表示非常量,使用 cref() 表示常量。

我还将使用

  • 赋值运算符(规则 3 所需的)
  • 来完成该课程。当你传播常量时有两个重载。

如果您计划将其用于 C++0x,还可以进行右值复制和分配,这在这种情况下很有用。

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

The compiler will select primarily an exact match before it finds a templated version but will pick a templated "exact match" over another function that fits, eg, one that uses a base class of your type.

Implicit conversions are dangerous and often bite you. It could simply be that way you are including your headers or the namespaces you are using.

I would do the following:

  • Make your constructors of Nullable all explicit. You do this with any constructors that take exactly one parameter, or can be called with one (even if there are more that have default values).

    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
    };
    
  • Replace the operator T& conversions with named functions. Use ref() for the non-const and cref() for the const.

I would also complete the class with

  • assignment operator (needed for rule of 3)
  • operator-> two overloads as you are propagating the constness.

If you plan to use this for C++0x also the r-value copy and assign, which is useful in this case.

By the way, you do know your deep copy won't work with base classes as they will slice.

素食主义者 2024-10-20 12:40:54

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

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

Well, since no real answer was found so far, I've made a workaround. Basically, I put the aforementioned Add functions in a seperate detail namespace, and added two template wrapper functions:

    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;
    }

I found this to always properly resolve to the correct one of those two functions, and the function for the actual contained type will be chosen in a seperate step inside these, as you can see.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文