【问题标题】:Select template return type from parameter从参数中选择模板返回类型
【发布时间】:2015-09-02 17:49:21
【问题描述】:

我有一些工作,但它似乎非常冗长。

#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 的单独模板布尔值 - 它不会扩展。

我能做些什么来收紧这个?

【问题讨论】:

  • 如果您将所有内容集中到一个类模板中,这样的事情会更易于管理。

标签: c++ templates


【解决方案1】:

您应该只转发到启用 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 对吗?)

【讨论】:

  • 如果它不是 Short 或 Long 它应该编译失败。未初始化的数据可能会出现 Short 或 Long。
  • @bizaff 然后你可以简单地删除(3) 重载。没有回退,所以它不会编译。
【解决方案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";
    }
}

【讨论】:

    【解决方案3】:

    我可以建议使用特征技术,就像其他答案一样。但是我的解决方案在允许此解决方案的可扩展性方面更好,我的意思是您的代码中不再有 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>
    {};
    

    【讨论】:

    • 我最终实现的与你的最接近,但略有不同 - template &lt;typename T&gt; struct BaseParseTypeTraits { static bool dataSet; typedef T dataType; static T data; }; template &lt;typename T&gt; bool BaseParseTypeTraits&lt;T&gt;::dataSet = false; template &lt;typename T&gt; typename BaseParseTypeTraits&lt;T&gt;::dataType BaseParseTypeTraits&lt;T&gt;::data; 然后专门研究 ParseTypeTraits 在 DataArrayLong 或 DataArrayShort 上派生自它的地方。我尝试发布带有这些更改的编辑,但被拒绝了。
    • @bizaff 我使用您的一些改进来编辑我的答案。而且我还添加了一些新想法……HTH
    猜你喜欢
    • 1970-01-01
    • 2018-02-21
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 2012-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多