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

时间:2015-09-02 17:49:21

标签: c++ templates

我有一些工作,但看起来非常冗长。

#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 - 它不会缩放。

我该怎样做才能收紧呢?

3 个答案:

答案 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的一种方便方法(因为毕竟,你可以从ShortLong继承权利吗?)

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