从参数中选择模板返回类型

Select template return type from parameter

本文关键字:返回类型 选择 参数      更新时间:2023-10-16

我有一些工作,但似乎太啰嗦了。

#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的方便方法(因为您毕竟可以从ShortLong继承,对吗?)

一点点的重构会有很长的路要走:

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>
{};