每个可变参数模板参数生成一个类成员

Posted

技术标签:

【中文标题】每个可变参数模板参数生成一个类成员【英文标题】:Generating one class member per variadic template argument 【发布时间】:2015-03-12 13:27:59 【问题描述】:

我有一个模板类,其中每个模板参数代表一种内部计算可以处理的值。需要模板(而不是函数重载),因为值是作为 boost::any 传递的,并且它们的类型在运行前不清楚。

为了正确地转换为正确的类型,我希望每个可变参数类型都有一个成员列表,如下所示:

template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass 
    std::vector<T1> m_argumentsOfType1;
    std::vector<T2> m_argumentsOfType2; // ...
;

或者,我想将模板参数类型存储在一个列表中,以便用它来做一些 RTTI 魔术(?)。但是我也不清楚如何将它们保存在 std::initializer_list 成员中。

感谢您的帮助!

【问题讨论】:

您可以将参数类型转发到std::tuple 它必须是单独的成员,还是可以拥有向量集合?就像例如向量的std::array? [我不知道如何解决这个问题,但你对这个问题的回答可能会帮助那些知道如何解决这个问题的人。] 收藏也可以。虽然我不确定这是否会有所帮助。在某些时候,容器(std::vector)只需要知道类型。 如果任何时候只填充一个向量,为什么不使用boost::variantvectors 呢?还是一次会填充多个? 另见:***.com/a/13462578/1599699 【参考方案1】:

正如您已经被暗示的那样,最好的方法是使用元组:

template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass 
    std::tuple<std::vector<AcceptedTypes>...> vectors;
;

这是乘以“字段”的唯一方法,因为您无法神奇地拼出字段名称。另一件重要的事情可能是对它们进行命名访问。我想您想要实现的是拥有多个具有 unique 类型的向量,因此您可以使用以下工具通过其值类型“搜索”正确的向量:

template <class T1, class T2>
struct SameType

    static const bool value = false;
;

template<class T>
struct SameType<T, T>

    static const bool value = true;
;

template <typename... Types>
class MyClass

     public:
     typedef std::tuple<vector<Types>...> vtype;
     vtype vectors;

     template<int N, typename T>
     struct VectorOfType: SameType<T,
        typename std::tuple_element<N, vtype>::type::value_type>
      ;

     template <int N, class T, class Tuple,
              bool Match = false> // this =false is only for clarity
     struct MatchingField
     
         static vector<T>& get(Tuple& tp)
         
             // The "non-matching" version
             return MatchingField<N+1, T, Tuple,
                    VectorOfType<N+1, T>::value>::get(tp);
         
     ;

     template <int N, class T, class Tuple>
     struct MatchingField<N, T, Tuple, true>
     
        static vector<T>& get(Tuple& tp)
        
            return std::get<N>(tp);
        
     ;

     template <typename T>
     vector<T>& access()
     
         return MatchingField<0, T, vtype,
                VectorOfType<0, T>::value>::get(vectors);
     
;

这是测试用例,您可以尝试一下:

int main( int argc, char** argv )

    int twelf = 12.5;
    typedef reference_wrapper<int> rint;

    MyClass<float, rint> mc;
    vector<rint>& i = mc.access<rint>();

    i.push_back(twelf);

    mc.access<float>().push_back(10.5);

    cout << "Test:\n";
    cout << "floats: " << mc.access<float>()[0] << endl;
    cout << "ints: " << mc.access<rint>()[0] << endl;
    //mc.access<double>();

    return 0;

如果您使用的任何类型不在您传递给专门化 MyClass 的类型列表中(请参阅this commented-out access for double),您将收到编译错误,不太可读,但 gcc 至少指出导致问题的正确位置,并且至少这样的错误消息表明问题的正确原因 - 例如,如果您尝试执行 mc.access(): p>

 error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’

【讨论】:

目前工作良好,谢谢!但是,如果我想要一个 vector<:reference_wrapper>> 怎么办?我收到以下错误:错误 C2504: 'std::tuple_element>' : base class undefined see reference to class template instantiation 'std::tuple_element>,std::allocator<:reference_wrapper int>>>>>>' 正在编译... #include &lt;functional&gt; 并在 C++11 模式下编译了吗?我稍微改变了我的测试用例,使用了reference_wrapper&lt;int&gt;,推送了一个int 变量,一切仍然正常。我把整个testcase函数放上来给大家看看。 非常感谢,它有效!触发编译器错误的原因是我有一些代码,如上面 Richard Hodges 所述,它试图自动将 boost::any 的向量插入到相应类型的成员向量中。 它必须有效,我花了相当大的努力来定义它 - 但它是值得的,即使作为我自己的练习:) 这是一个很棒的模板魔法,我从中学到了一些有趣的东西。谢谢!我有一个担忧——它看起来像是创建了一组模板函数,每个模板类型一个。对于第 N 个类型,生成函数的调用堆栈将是 N 级深,因为 MatchingField::get() 会递归调用自身。我的问题是:编译器是否能够将其压缩为一个函数,甚至可以压缩为单个内联指针取消引用?由于在运行时一切都是静态的,看起来应该是可能的,但我不是 100% 确定。【参考方案2】:

不使用元组的替代解决方案是使用 CRTP 创建一个类层次结构,其中每个基类都是其中一种类型的特化:

#include <iostream>
#include <string>

template<class L, class... R> class My_class;

template<class L>
class My_class<L>

public:

protected:
  L get()
  
    return val;
  

  void set(const L new_val)
  
    val = new_val;
  

private:
  L val;
;

template<class L, class... R>
class My_class : public My_class<L>, public My_class<R...>

public:
  template<class T>
  T Get()
  
    return this->My_class<T>::get();
  

  template<class T>
  void Set(const T new_val)
  
    this->My_class<T>::set(new_val);
  
;

int main(int, char**)

  My_class<int, double, std::string> c;
  c.Set<int>(4);
  c.Set<double>(12.5);
  c.Set<std::string>("Hello World");

  std::cout << "int: " << c.Get<int>() << "\n";
  std::cout << "double: " << c.Get<double>() << "\n";
  std::cout << "string: " << c.Get<std::string>() << std::endl;

  return 0;

【讨论】:

我认为这要求每个元素都有一个唯一的类型。有办法解决吗? 与元组解决方案相比,此解决方案的一个优势是,当数据类型未对齐时,此解决方案将使用更少的内存。例如,一个 uint32 和 uint16 的元组大小是 8 个字节,而不是 6 个。 @Eyal,对于您给出的示例,此解决方案还应产生 8 个字节的大小。 uint32 将导致类的最小对齐为 4 个字节,因此从第二个父类添加的 uint16 的 2 个字节需要后跟 2 个填充字节才能正确对齐类。即使您交换父类,您仍然应该以 8 个字节结束,因为 uint16 在它之后仍然需要 2 个填充字节,以便它之后的 uint32 正确对齐。所以无论哪种方式,除非你使用一些非标准的打包编译指示,否则你应该以 8 个字节结束。 总的来说,基于元组的解决方案似乎是更有效的解决方案,在大小方面。例如,如果您有一个 uint32、uint16、uint8 和 uint64,按此顺序,基于元组的解决方案导致类大小为 16 个字节,而基于继承 (this) 的解决方案导致类大小为 32使用最新版本的 clang 和 gcc 的字节(尚未使用 msvc 测试)。你可以在这里看到它的实际效果:godbolt.org/z/vfd6zqzdz。如果将类型从大到小排序,两种情况下的效率相同,但对于更复杂的类型可能会很困难。【参考方案3】:

正如 πάντα-ῥεῖ 的评论中提到的那样,一种方法是使用元组。他没有解释(可能是为了避免你自己)是它的样子。

这是一个例子:

using namespace std;

// define the abomination    
template<typename...Types>
struct thing

    thing(std::vector<Types>... args)
    : _x  std::move(args)... 
    

    void print()
    
        do_print_vectors(std::index_sequence_for<Types...>());
    

private:
    template<std::size_t... Is>
    void do_print_vectors(std::index_sequence<Is...>)
    
        using swallow = int[];
        (void)swallow0, (print_one(std::get<Is>(_x)), 0)...;
    

    template<class Vector>
    void print_one(const Vector& v)
    
        copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
        cout << endl;
    

private:
    tuple<std::vector<Types>...> _x;
;


// test it
BOOST_AUTO_TEST_CASE(play_tuples)

    thing<int, double, string> t 
         1, 2, 3, ,
         1.1, 2.2, 3.3 ,
         "one"s, "two"s, "three"s 
    ;

    t.print();

预期输出:

1,2,3,
1.1,2.2,3.3,
one,two,three,

【讨论】:

啊,我错过了采用向量参数的额外构造函数。我在搞乱std::tuple&lt;std::vector&gt;&gt; 的初始化列表并遇到了这些构造函数的显式性质。 这是一个有趣的练习,但如果我在我们的生产代码中看到这一点,我会想解雇某人:-) 这种类型的运动可以有好处,例如在面向数据的设计中,由于更好的缓存等原因,您经常查看std::tuple&lt;std::vector&gt; 而不是std::vector&lt;std::tuple&gt;。但是在前者的数据布局之上提供后者的接口可能很方便。如果你想避免手动编码这样的转换,一些可变元组魔法可以派上用场! @RichardHodges 很高兴知道你不是我的老板 ;)!我有something like this in production。不幸的是,这是我最不赞成的问题之一(我认为 DV 只是被有意选择的报复)。 谢谢!该代码取决于我的 VS2013 不可用的 c++14 功能。类似的东西只用 c++11 可行吗?【参考方案4】:

有一个建议允许这种扩展,使用直观的语法:P1858R1 Generalized pack declaration and usage。您还可以初始化成员并通过索引访问它们。你甚至可以通过写using... tuple_element = /*...*/来支持结构化绑定:

template <typename... Ts>
class MyClass 
    std::vector<Ts>... elems;
public:
    using... tuple_element = std::vector<Ts>;

    MyClass() = default;
    explicit MyClass(std::vector<Ts>... args) noexcept
        : elems(std::move(args))...
    
    

    template <std::size_t I>
        requires I < sizeof...(Ts)
    auto& get() noexcept
    
        return elems...[I];
    

    template <std::size_t I>
        requires I < sizeof...(Ts)
    const auto& get() const
    
        return elems...[I];
    

    // ...
;

那么类可以这样使用:

using Vecs = MyClass<int, double>;

Vecs vecs;
vecs.[0].resize(3, 42);

std::array<double, 4> arr1.0, 2.0, 4.0, 8.0;
vecs.[1] = arr.[:];

// print the elements
// note the use of vecs.[:] and Vecs::[:]
(std::copy(vecs.[:].begin(), vecs.[:].end(),
           std::ostream_iterator<Vecs::[:]>std::cout, ' ',
 std::cout << '\n'), ...);

【讨论】:

【参考方案5】:

这是一个使用boost::variant 的不太高效的实现:

template<typename ... Ts>
using variant_vector = boost::variant< std::vector<Ts>... >;

template<typename ...Ts>
struct MyClass 
  using var_vec = variant_vector<Ts...>;
  std::array<var_vec, sizeof...(Ts)> vecs;
;

我们创建了一个变体向量,可以在其中保存一个类型列表。你必须使用boost::variant 来获取内容(这意味着知道内容的类型,或者写一个访问者)。

然后我们存储这些变体向量的数组,每种类型一个。

现在,如果您的类只保存一种类型的数据,您可以取消数组,而只拥有一个 var_vec 类型的成员。

我不明白你为什么想要每种类型的一个向量。我可以看到需要一个向量,其中每个元素都是任何类型之一。那将是vector&lt;variant&lt;Ts...&gt;&gt;,而不是上面的variant&lt;vector&lt;Ts&gt;...&gt;

variant&lt;Ts...&gt;boost union-with-type。 anyboost smart-void*optionalboost there-or-not。

template<class...Ts>
boost::optional<boost::variant<Ts...>> to_variant( boost::any );

可能是一个有用的函数,它接受any 并尝试将其转换为variant 中的任何Ts... 类型,如果成功则返回它(如果不成功则返回空optional )。

【讨论】:

以上是关于每个可变参数模板参数生成一个类成员的主要内容,如果未能解决你的问题,请参考以下文章

如何在可变参数类模板中获取类型的索引?

可变参数模板模板和完美转发

为每个可变参数模板参数和一个数组调用一个函数

模板参数迭代

如何使用可变参数模板参数保存可变数量的参数?

C++11 ——— 可变参数模板