模板函数重载未按预期调用
我的情况如下:
我有一个模板包装器,它可以处理值和对象可为空的情况,而无需手动处理指针甚至 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
编译器在找到模板化版本之前将主要选择精确匹配,但会在另一个适合的函数(例如使用您类型的基类的函数)上选择模板化的“精确匹配”。
隐式转换很危险,而且常常会咬你一口。这可能只是您包含标头或您正在使用的名称空间的方式。
我会执行以下操作:
使 Nullable 的构造函数全部显式。您可以使用任何只接受一个参数的构造函数来执行此操作,或者可以使用一个参数进行调用(即使还有更多具有默认值的构造函数)。
替换运算符T&与命名函数的转换。使用 ref() 表示非常量,使用 cref() 表示常量。
我还将使用
如果您计划将其用于 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).
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
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.
好吧,由于到目前为止还没有找到真正的答案,所以我做了一个解决方法。基本上,我将上述
Add
函数放在单独的detail
命名空间中,并添加了两个模板包装函数:我发现这始终可以正确解析为这两个函数之一,并且将在其中的单独步骤中选择实际包含类型的函数,如您所见。
Well, since no real answer was found so far, I've made a workaround. Basically, I put the aforementioned
Add
functions in a seperatedetail
namespace, and added two template wrapper functions: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.