如何使用(可变参数)模板进一步概括这一点

时间:2014-05-08 15:30:48

标签: c++ templates variadic-templates

我无法在模板化代码中进行第二步或第二步。为了便于阅读,我已经将代码剥离了它的基本要素。

我查看了很多模板问题,但我真的无法解决我的确切问题。

我目前有一个类RIVRecord,我模仿这个

template <class T>
class RIVRecord
{

private:
    std::vector<T> values;
public:

    std::string name;

    RIVRecord(std::string _name, std::vector<T> _values) { name = _name; values = _values;  };
    ~RIVRecord(void) {  };

    size_t size() {
        return values.size();
    }

    T* Value(int index) {
        return &values[index];
    }
}

够容易。 T类型通常是原始类型,例如浮点数和整数。然后我想把这些RIVRecords放在DataSet类中。这是我遇到更多困难的地方。没有模仿它会是这样的:

class RIVDataSet
{
private :
    //How to template this??
    vector<RIVRecord<float>> float_records;
    vector<RIVRecord<int>> int_records;

public:
    RIVDataSet(void);
    ~RIVDataSet(void);
    //And this
    void AddRecord(RIVRecord<float> record) {
        //How would this work?    
    }
    //And this?
    RIVRecord<float> GetFloatRecord();
};

我来自Java背景,所以我可以使用vector<?>并在我询问RIVRecord时进行类型检查。但这在C ++中似乎不可能。我尝试使用可变参数模板但不确定如何使用模板中的所有类型构建向量:

template <class... Ts>
class RIVDataSet
{
private :        
    //For each T in Ts
    vector<RIVRecord<T>> records;

public:
    RIVDataSet(void);
    ~RIVDataSet(void);
    //For each T in Ts
    void AddRecord(RIVRecord<T> record) {
        //How would this work?    
    }
    //For each T in Ts, get the record by index.
    RIVRecord<T> GetRecord(int index);
};

我已经看到C ++模板中的这种迭代是不可能的,但它只是为了澄清我想要的东西。

非常欢迎任何帮助,谢谢。

编辑:

对于T

,类型(浮点数,整数,......)的数量没有限制

此外,GetRecord通过索引来工作,但我并不那么关心它,只要我可以迭代记录并获得正确的类型。

4 个答案:

答案 0 :(得分:6)

通过可变参数模板解决这个问题并不是很复杂,但需要一些额外的支持类型。让我们从查看结果开始:

template <typename... V>
class many_vectors
{
    static_assert(are_all_different<V...>::value, "All types must be different!");

    std::tuple<std::vector<V>...> _data;

public:
    template<typename T>
    std::vector<T>& data()
    { return std::get<index_of<T, V...>::value>(_data); }

    template<typename T>
    std::vector<T> const& data() const
    { return std::get<index_of<T, V...>::value>(_data); }

    template<typename T>
    void push_back(T&& arg)
    { data<typename std::remove_reference<T>::type>().push_back(std::forward<T>(arg)); }

    template<typename T, typename... W>
    void emplace_back(W&&... args)
    { data<T>().emplace_back(std::forward<W>(args)...); }
};

static_assert定义了一个非常重要的要求:由于我们在区分类型,我们必须确保所有类型都不同。 _data成员是不同类型的向量的std::tuple,并且直接对应于您的float_recordsint_records成员。

作为提供按其类型引用其中一个向量的成员函数的示例,data函数公开单个vector。它使用辅助模板来确定tuple的哪个元素对应于您的类型并获得结果。

还展示了向量的push_back函数,以展示如何使用它来提供这些函数。这里std::forward用于在参数上实现完美转发以提供最佳性能。但是,将rvalue引用与模板参数推导结合使用可能会导致意外结果。因此,删除了对T参数的任何引用,因此push_back不适用于包含引用类型的many_vectors。这可以通过提供两个重载push_back<T>(T&)push_back<T>(T const&)来解决。

最后,emplace_back公开了一个函数,该函数不能依赖模板参数参数推导来确定它应该使用哪个向量。通过首先保留T模板参数,我们允许使用仅显式指定T的使用方案。

使用此功能,您应该干练地实现具有类似功能的任意其他成员(例如begin<T>end<T>)。

助手

最重要的帮手很简单:

template<typename T, typename U, typename... V>
struct index_of : std::integral_constant<size_t, 1 + index_of<T, V...>::value>
{ };

template<typename T, typename... V>
struct index_of<T, T, V...> : std::integral_constant<size_t, 0>
{ };

如果第一个参数根本不是以下参数之一,那么这将失败并显示相当丑陋的错误消息,因此您可能希望对此进行改进。

另一个助手并不复杂:

template<typename T, typename... V>
struct is_different_than_all : std::integral_constant<bool, true>
{ };

template<typename T, typename U, typename... V>
struct is_different_than_all<T, U, V...>
    : std::integral_constant<bool, !std::is_same<T, U>::value && is_different_than_all<T, V...>::value>
{ };

template<typename... V>
struct are_all_different : std::integral_constant<bool, true>
{ };

template<typename T, typename... V>
struct are_all_different<T, V...>
    : std::integral_constant<bool, is_different_than_all<T, V...>::value && are_all_different<V...>::value>
{ };

用法

是的,用法非常简单:

v.push_back(int(3));
v.push_back<float>(4);
v.push_back<float>(5);
v.push_back(std::make_pair('a', 'b'));
v.emplace_back<std::pair<char, char>>('c', 'd');

std::cout << "ints:\n";
for(auto i : v.data<int>()) std::cout << i << "\n";

std::cout << "\n" "floats:\n";
for(auto i : v.data<float>()) std::cout << i << "\n";

std::cout << "\n" "char pairs:\n";
for(auto i : v.data<std::pair<char, char>>()) std::cout << i.first << i.second << "\n";

预期结果:

ints:
3

floats:
4
5

char pairs:
ab
cd

答案 1 :(得分:1)

您可以使用称为类型擦除的技术,但是您必须包含另一个间接级别。一些一般性反馈:

RIVRecord(std::string _name, std::vector<T> _values)

更好的是:

RIVRecord(const std::string& _name, const std::vector<T>& _values)

为了避免不必要的副本,总体而言,经验法则是接受const&参数作为大多数不是原始的东西。

T* Value(int index) { return &values[index]; }

很危险,如果size()超出capacity()的{​​{1}},它将重新分配并使您的所有指针无效。我认为更好的界面是拥有vector< T >&amp; T GetValue< T >()

打开类型擦除,这就是void SetValue< T >( T a_Value )看起来的样子,我在这里使用一个名为Loki的库,如果你不想使用Loki,我会在之后给你一些指示。

RIVDataSet

class RIVDataSet { private : //How to template this?? struct HolderBase { virtual ~HolderBase() {} }; template< typename T > struct HolderImpl : HolderBase { // Use pointer to guarantee validity of returned record std::vector< RIVRecord< T >* > m_Record; }; typedef Loki::AssocVector< Loki::TypeInfo, HolderBase* > HolderMap; HolderMap m_Records; public: ~RIVDataSet() { for( HolderMap::iterator itrCur = m_Records.begin(); itrCur != m_Records.end(); ++itrCur ) delete itrCur->second; } //And this template< typename T > void AddRecord(const RIVRecord< T >& record ) { HolderMap::iterator itrFnd = m_Records.find( typeid( T ) ); if( itrFnd == m_Records.end() ) itrFnd = m_Records.insert( std::make_pair( Loki::TypeInfo( typeid( T ) ), new HolderImpl< T >() ) ).first; static_cast< HolderImpl< T >* >( itrFnd->second )->m_Record.push_back( new RIVRecord< T >( record ) ); } template< typename T > RIVRecord< T >* GetRecord() { HolderMap::iterator itrFnd = m_Records.find( typeid( T ) ); assert( itrFnd != m_Records.end() ); return itrFnd == m_Records.end() ? 0 : static_cast< HolderImpl< T >* >( itrFnd->second )->m_Record.front(); } }; 可替代Loki::AssocVector,但您需要std::map,这只是Loki::TypeInfo的包装。如果你在std::type_info中查看代码,可以很容易地实现自己。

答案 2 :(得分:0)

您不能使用可变参数模板来创建同名但不同类型的多个成员。实际上,您永远不会有两个具有相同名称的成员。但是,您可以使用多重继承,并使用可变参数基类将该成员放在基类中。然后,您可以在派生类中使用成员模板来解决歧义。

下面的示例还使用完美转发来确保如果临时传递给add(),其资源可能会“被盗”。您可以阅读有关here的更多信息。

以下是示例:

#include <vector>
#include <utility>

// This templated base class holds the records for each type.
template <typename T>
class Base {
    public:
        // "T &&v" is a universal reference for perfect forwarding.
        void add(T &&v) { records.push_back(std::forward<T>(v)); }
    private:
        std::vector<T> records;
};

// This inherits from Base<int>, Base<double>, for example, if you instantiate
// DataSet<int, double>.
template <typename... Ts>
class DataSet : public Base<Ts>... {
    public:
        // The purpose of this member template is to resolve ambiguity by specifying
        // which base class's add() function we want to call.  "U &&u" is a
        // universal reference for perfect forwarding.
        template <typename U>
        void add(U &&u) {
            Base<U>::add(std::forward<U>(u));
        }
};

int main() {
    DataSet<int, double> ds;
    ds.add(1);
    ds.add(3.14);
}

答案 3 :(得分:0)

一个可怕的想法,如果你真的必须这样做,一般是使用&#34;类型擦除成语&#34;。它是这样的(虽然但我认为它会被编译,并且可以通过将RIVRecordsIndex :: Float链接到类型float并防止错误的类型特征进一步改进)

class BaseRIVRecord                                                                                                                                          
{                                                                                                                                                            
};                                                                                                                                                           

template <class T>                                                                                                                                           
class RIVRecord : public BaseRIVRecord                                                                                                                       
{                                                                                                                                                            
};                                                                                                                                                           

enum class RIVRecordsIndex                                                                                                                                   
{                                                                                                                                                            
   Float, Int                                                                                                                                                   
};                                                                                                                                                           

class RIVDataSet                                                                                                                                             
{                                                                                                                                                            
public:                                                                                                                                                         
    template<RIVRecordsIndex I, typename T>                                                                                                                                         
    void addRecord()                                                                                                                                
    {                                                                                                                                                            
         allmightyRecords.resize(I+1);                                                                                                                            
         allmightyRecords[I].push_back(new RIVRecord<T>());                                                                                                       
    }                                                                                                                                                            

    template<RIVRecordsIndex I, typename T>                                                                                                                                         
    RIVRecord<T>* get(unsigned int index)                                                                               
    {                                                                                                                                                            
         return static_cast<RIVRecord<T>*>(allmighyRecords[I][index]);                                                                               
    }
private:                                                                                                                                                            
    std::vector<std::vector<BaseRIVRecord*>> allmightyRecords;                                                                                                 
};                                                                                                                                                           

int main()
{
     RIVDataSet set;
     set.addRecord<RIVRecordsIndex::Float, float>();
     set.addRecord<RIVRecordsIndex::Float, float>();
     set.addRecord<RIVRecordsIndex::Int, int>();

     RIVRecord<int> r = set.get<RIVRecordsIndex::Int, int>(0);
}

如果您决定这样做,请确保不切片继承的类型(即使用指针向量)。请使用某种类型的特征来防止像set.get这样的错误调用。我再也没有时间去编译它,这只是一个进一步发展的想法。