从参数中选择模板返回类型
Select template return type from parameter
我有一些工作,但似乎太啰嗦了。
#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()。在parse中,我想确保路由到正确的标志和数据来实例化ConcreteType。
我开始认为我不能使用函数模板来做我想做的事情-我最好使用带有静态函数成员的类模板。
使用std::is_base_of似乎很笨拙-我可以使用内置继承重载而不是基于Short和Long重载的is_base_of吗?
RetType似乎没有必要,但似乎没有其他方法来声明getData()。
部分困难在于,我需要在实例化t之前确定要初始化它的数据。
我不喜欢IsShort和IsLong单独的模板-它不会缩放。
我该怎么做才能把它拉紧呢?
您应该转发到启用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
继承,对吗?)
一点点的重构会有很长的路要走:
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";
}
}
我可以建议使用特征技术,如其他答案。但我的解决方案是更好的方式,它允许这个解决方案的可伸缩性,我的意思是没有更多的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
(更新)在这个示例代码中,我还添加了添加新基和数据类型所需的内容。
我的意思是你有这个:
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>
{};
- 如何获取std::result_of函数的返回类型
- 奇怪的结构&GCC&clang(void*返回类型)
- 如何建立使用模板函数的lambda函数的尾部返回类型
- 为什么与常规GCC不同,即使有"学究性错误",MinGW-GCC也能容忍丢失的返回类型
- 在没有定义返回类型的函数中返回布尔值,并将结果保存在无错误的char编译中-为什么
- 特征::矩阵<双精度,1,3> 结构类型函数中的返回类型函数
- 函数作为模板参数,是否对返回类型强制约束
- C++中函数的向量返回类型引发错误
- 检查函数返回类型是否与STL容器类型值相同
- 为什么返回类型中需要typename?C++
- <Windows>为什么 std::thread::native_handle 返回类型为"long long unsigned int"的值,而不是 void*(又名 HANDLE)?
- 警告:在函数返回类型 [-Wignore 限定符] 时忽略类型限定符
- 使用 std::function 时选择自动返回类型而不是构造函数的调用运算符
- C++ 在列表和列表之间选择返回类型<<string>std::p air<string,string>>
- 返回类型在C 中选择
- C++多态性+用于选择返回类型的模板成员函数.如何做到这一点
- 函数返回类型是否会影响重载的选择
- 为什么在返回从函数的返回类型派生的类型本地对象时不选择 move 构造函数?
- 从参数中选择模板返回类型
- 如何根据返回类型选择重载