开发者

Template function overload not called as expected

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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜