我试图或多或少一般地序列化模板类MState<T>
。为此,我有一个父抽象类MVariable
,它使用以下形式实现了几个序列化函数:
template <class Serializer, class SerializedType>
void serialize(Serializer& s, const SOME_SPECIFIC_TYPE &t) const;
我想允许T
几乎任何东西。序列化通过RapidJSON::Writer在JSON中完成。因此,我需要使用特定的成员函数(例如Writer::String
,Writer::Bool
,Writer::Uint
...)以获得每种类型T
的正确格式。
MVariable
将提供基本类型和STL容器的序列化。然而,我没有提供每一种类型(例如用SOME_SPECIFIC_TYPE
,float
,double
等替换bool
),而是试图实现一个基于SFINAE的解决方案,似乎有一些缺陷。
我有一组typedef定义和序列化函数,如下所示:
class MVariable
{
template <class SerT> using SerializedFloating =
typename std::enable_if<std::is_floating_point<SerT>::value, SerT>::type;
template <class SerT> using SerializedSeqCntr =
typename std::enable_if<is_stl_sequential_container<SerT>::value, SerT>::type;
/* ... and many others. */
/* Serialization of float, double, long double... */
template <class Serializer, class SerializedType>
void serialize(Serializer& s, const SerializedFloating<SerializedType> &t) const {
s.Double(t);
}
/* Serialization of vector<>, dequeue<>, list<> and forward_list<> */
template <class Serializer, class SerializedType>
void serialize(Serializer& s, const SerializedSeqCntr<SerializedType> &t) const {
/* Let's assume we want to serialize them as JSON arrays: */
s.StartArray();
for(auto const& i : t) {
serialize(s, i); // ----> this fails to instantiate correctly.
}
s.EndArray();
}
/* If the previous templates could not be instantiated, check
* whether the SerializedType is a class with a proper serialize
* function:
**/
template <class Serializer, class SerializedType>
void serialize(Serializer&, SerializedType) const
{
/* Check existance of:
* void SerializedType::serialize(Serializer&) const;
**/
static_assert(has_serialize<
SerializedType,
void(Serializer&)>::value, "error message");
/* ... if it exists then we use it. */
}
};
template <class T>
class MState : public MVariable
{
T m_state;
template <class Serializer>
void serialize(Serializer& s) const {
s.Key(m_variable_name);
MVariable::serialize<Serializer, T>(s, m_state);
}
};
is_stl_sequential_container
的实施基于this,has_serialize
的实施是从here借来的。两者都经过检查,似乎正常工作:
MState<float> tvar0;
MState<double> tvar1;
MState<std::vector<float> > tvar2;
rapidjson::StringBuffer str_buf;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(str_buf);
writer.StartObject();
tvar0.serialize(writer); /* --> First function is used. Ok! */
tvar1.serialize(writer); /* --> First function is used. Ok! */
tvar2.serialize(writer); /* --> Second function is used, but there's
* substitution failure in the inner call.
**/
writer.EndObject();
但是,第二个函数内部的递归serialize
调用无法实例化。编译器抱怨从这开始:
In instantiation of ‘void MVariable::serialize(Serializer&, SerializedType) const
[with Serializer = rapidjson::PrettyWriter<... blah, blah, blah>;
SerializedType = float]’:
消息继续静态断言错误,表明所有先前重载的模板函数在其替换中失败,或者最后一个是最佳选项。
为什么替换“失败”在这里为float
而不是当我尝试序列化tvar0
或tvar1
?
您的代码中至少存在两个问题。
首先,您在MState::serialize()
中明确指定模板参数:
MVariable::serialize<Serializer, T>(s, m_state);
但是你在SerializedSeqCntr
约束的重载中调用模板类型推导(通过serialize(s, i);
);这不起作用,因为那些SFINAE检查是非推导的上下文(*),也就是说,它们不会在类型推导中分离,编译器无法推断出SerializedType
类型。
要么明确地传递参数,就像在
serialize<Serializer,std::decay_t<decltype(i)>>(s, i);
或添加推导的SerializedType const&
参数和sfinae约束的伪默认参数或返回类型(**)。
第二个问题是'fallback'重载应该在可能调用它的约束重载之前:
template <class Serializer, class SerializedType>
void serialize(Serializer&, SerializedType) const:
template <class Serializer, class SerializedType>
void serialize(Serializer& s, const SerializedSeqCntr<SerializedType> &t);
...
否则,名字查找将无法在serialize()
约束的重载内找到正确的SerializedSeqCntr
。是的,作为一个依赖名称的函数,名称查找确实发生在实例化点;但是,只考虑在函数体上下文中可见的名称(除非ADL启动)。
也可能还有第三个问题;因为前者按值采用SerializedType,所以后备重载不优于约束重载;如果这不是意图,你还需要进一步限制后备。
(*)详细说明一下,当你调用一个函数模板时,你要么显式传递模板参数(如在foo<bar>()
中),要么让编译器从函数参数的类型中推导出它们(如在foo(some_bar)
中)。有时,这个过程不能成功。
出现这种情况有三个原因:
const SerializedFloating<SerializedType>&
这个论点确实是非推论的。(**)如前所述,SFINAE约束通常是非推断的;所以,如果你需要进行类型推导,你应该在自己的可推导论证中传递待推导的参数;这通常通过添加伪默认参数或通过返回类型来完成:
template<typename T>
result_type
foo( T arg, std::enable_if_t<std::is_floating_point<T>::value>* = 0 );
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value, result_type>
foo( T arg );