我有一些工作,但看起来非常冗长。
#include <array>
#include <iostream>
#include <type_traits>
using DataArrayShort = std::array<unsigned char, 4>;
using DataArrayLong = std::array<unsigned char, 11>;
// Two base classes the later template stuff should choose between
class Short
{
public:
Short(const DataArrayShort & data) { /* do some init */}
};
class Long
{
public:
Long(const DataArrayLong & data) { /* do some init */}
};
// Concrete derived of the two bases
class S1 : public Short
{
public:
using Short::Short;
operator std::string() { return "S1!";}
};
class S2 : public Short
{
public:
using Short::Short;
operator std::string() { return "S2!";}
};
class L1 : public Long
{
public:
using Long::Long;
operator std::string() { return "L1!";}
};
class L2 : public Long
{
public:
using Long::Long;
operator std::string() { return "L2!";}
};
// Variables that will be modified by parsing other things before calling parse<>()
bool shortDataSet = false;
bool longDataSet = false;
DataArrayShort shortData;
DataArrayLong longData;
// Begin overly verbose template stuff
template<bool IsShort, bool IsLong>
bool getFlag();
template<>
bool getFlag<true, false>()
{
return shortDataSet;
}
template<>
bool getFlag<false, true>()
{
return longDataSet;
}
template<bool IsShort, bool IsLong>
struct RetType
{};
template<>
struct RetType<true, false>
{
typedef DataArrayShort & type;
};
template<>
struct RetType<false, true>
{
typedef DataArrayLong & type;
};
template<bool IsShort, bool IsLong>
typename RetType<IsShort, IsLong>::type getData();
template<>
DataArrayShort & getData<true, false>()
{
return shortData;
}
template<>
DataArrayLong & getData<false, true>()
{
return longData;
}
template<typename T>
inline std::string parse()
{
// First test if I can create the type with initialized data
if (getFlag<std::is_base_of<Short, T>::value, std::is_base_of<Long, T>::value>())
{
// If it's initialized, Then create it with the correct array
T t(getData<std::is_base_of<Short, T>::value, std::is_base_of<Long, T>::value>());
return t;
}
else
{
return "with uninitialized data";
}
}
// End overly verbose template stuff
int main(int argc, const char * argv[])
{
// Something things that may or may not set shortDataSet and longDataSet and give shortData and longData values
std::cout << parse<S1>() << std::endl;
shortDataSet = true;
std::cout << parse<S1>() << std::endl;
std::cout << parse<L2>() << std::endl;
longDataSet = true;
std::cout << parse<L2>() << std::endl;
}
对我来说重要的语法是parse()。在解析中,我想确保我路由到正确的标志和数据以实例化ConcreteType。
我开始认为我无法使用功能模板来做我想做的事情 - 我最好使用带有静态功能成员的类模板。
使用std :: is_base_of看起来很笨 - 我可以使用带有重载的内置继承而不是带有基于Short和Long的重载的is_base_of吗?
RetType似乎没必要,但似乎没有其他方法来声明getData()。
部分困难在于我需要在实例化之前确定要初始化t的数据。
我不喜欢IsShort和IsLong的单独模板bool - 它不会缩放。
我该怎样做才能收紧呢?
答案 0 :(得分:2)
您应该转发到启用了SFINAE的调度程序。从继承树开始:
template <int I> struct chooser : chooser<I-1> { };
template <> struct chooser<0> { };
转发给它:
template <typename T>
std::string parse() { return parse_impl<T>(chooser<2>{}); }
写下你的案例:
template <typename T,
typename = std::enable_if_t<std::is_base_of<Short, T>::value>
>
std::string parse_impl(chooser<2> ) { // (1)
// we're a Short!
if (shortDataSet) {
return T{shortData};
}
else {
return "with uninitialized data";
}
}
template <typename T,
typename = std::enable_if_t<std::is_base_of<Long, T>::value>
>
std::string parse_impl(chooser<1> ) { // (2)
// we're a Long!
if (longDataSet) {
return T{longData};
}
else {
return "with uninitialized data";
}
}
template <typename >
std::string parse_impl(chooser<0> ) { // (3)
// base case
return "with uninitialized data";
}
如果T
继承自Short
,则会调用(1)
。否则,如果它继承自Long
,则调用(2)
。否则,调用(3)
。这是在多个可能重叠的标准上执行SFINAE的一种方便方法(因为毕竟,你可以从Short
和Long
继承权利吗?)
答案 1 :(得分:2)
一点点的重构还有很长的路要走:
template<class T, bool IsShort = std::is_base_of<Short, T>::value,
bool IsLong = std::is_base_of<Long, T>::value>
struct data_traits { };
template<class T>
struct data_traits<T, true, false> {
static bool getFlag() { return shortDataSet; }
static DataArrayShort & getData() { return shortData; }
};
template<class T>
struct data_traits<T, false, true> {
static bool getFlag() { return longDataSet; }
static DataArrayLong & getData() { return longData; }
};
template<typename T>
inline std::string parse()
{
using traits = data_traits<T>;
// First test if I can create the type with initialized data
if (traits::getFlag())
{
// If it's initialized, Then create it with the correct array
T t(traits::getData());
return t;
}
else
{
return "with uninitialized data";
}
}
答案 2 :(得分:0)
我可以建议使用特征技术,就像其他答案一样。但是我的解决方案更好,因为它允许此解决方案的可用性,我的意思是代码中不再有true, false, ...
个标志;)
从这条评论开始:
// Variables that will be modified by parsing other things before calling parse<>()
将您的代码更改为更具可扩展性的版本。
首先使用数据类型连接基类型:
template <typename BaseType>
class BaseDataTypeTraits;
template <> struct BaseDataTypeTraits<Short>
{
typedef DataArrayShort DataType;
};
template <> struct BaseDataTypeTraits<Long>
{
typedef DataArrayLong DataType;
};
然后定义基本类型特征:
template <typename BaseType>
struct BaseParseTypeTraits
{
static bool dataSet;
typedef typename BaseDataTypeTraits<BaseType>::DataType DataType;
static DataType data;
};
template <typename BaseType>
bool BaseParseTypeTraits<BaseType>::dataSet = false;
template <typename BaseType>
typename BaseParseTypeTraits<BaseType>::DataType BaseParseTypeTraits<BaseType>::data;
并解析每种特定基本类型的特征:
template <typename T, typename EnableIf = void>
class ParseTypeTraits;
template <typename T>
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<Short, T>::value>::type>
: public BaseParseTypeTraits<Short>
{};
template <typename T>
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<Long, T>::value>::type>
: public BaseParseTypeTraits<Long>
{};
然后你的解析几乎与其他“特征”答案相同:
template<typename T>
inline std::string parse()
{
typedef ParseTypeTraits<T> TTraits;
// First test if I can create the type with initialized data
if (TTraits::dataSet)
{
// If it's initialized, Then create it with the correct array
T t(TTraits::data);
return t;
}
else
{
return "with uninitialized data";
}
}
int main(int argc, const char * argv[])
{
// Something things that may or may not set shortDataSet and longDataSet and give shortData and longData values
std::cout << parse<S1>() << std::endl;
BaseParseTypeTraits<Short>::dataSet = true;
std::cout << parse<S1>() << std::endl;
std::cout << parse<L2>() << std::endl;
BaseParseTypeTraits<Long>::dataSet = true;
std::cout << parse<L2>() << std::endl;
}
工作示例:ideone
[UPDATE]
在这个示例代码中,我还添加了添加新基数和数据类型所需的内容。
我的意思是你有这个:
using DataArrayNew = std::array<unsigned char, 200>;
class New
{
public:
New(const DataArrayNew & data) { /* do some init */}
};
class N1 : public New
{
public:
using New::New;
operator std::string() { return "N1!";}
};
要使解析支持这些类型 - 您只需要这两个专业化:
template <> struct BaseDataTypeTraits<New>
{
typedef DataArrayNew DataType;
};
template <typename T>
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<New, T>::value>::type>
: public BaseParseTypeTraits<New>
{};
这可以包含在宏中:
#define DEFINE_PARSE_TRAITS_TYPE(BaseTypeParam, DataTypeParam) \
template <> struct BaseDataTypeTraits<BaseTypeParam> \
{ \
typedef DataTypeParam DataType; \
}; \
template <typename T> \
class ParseTypeTraits<T, \
typename std::enable_if< \
std::is_base_of<BaseTypeParam, T>::value>::type> \
: public BaseParseTypeTraits<BaseTypeParam> \
{}
所以对新类型的支持就像这样简单:
DEFINE_PARSE_TRAITS_TYPE(New, DataArrayNew);
当我们可以要求基类型在其类定义中定义其数据类型时,可以实现更多的简化 - 就像这里:
class New
{
public:
typedef DataArrayNew DataType;
New(const DataArrayNew & data) { /* do some init */}
};
然后我们可以使用泛型BaseDataTypeTraits定义:
template <typename BaseType>
struct BaseDataTypeTraits
{
typedef typename BaseType::DataType DataType;
};
因此对于新类型 - 您只需要为DataTypeTraits添加特化:
template <typename T>
class ParseTypeTraits<T, typename std::enable_if<std::is_base_of<New, T>::value>::type>
: public BaseParseTypeTraits<New>
{};