繁体   English   中英

将struct的向量实际上转换为struct成员的向量

[英]Virtually turn vector of struct into vector of struct members

我有一个函数,需要类似矢量的输入。 为简化起见,让我们使用以下print_in_order函数:

#include <iostream>
#include <vector>

template <typename vectorlike>
void print_in_order(std::vector<int> const & order,
                    vectorlike const & printme) {
    for (int i : order)
        std::cout << printme[i] << std::endl;
}

int main() {
    std::vector<int> printme = {100, 200, 300};
    std::vector<int> order = {2,0,1};
    print_in_order(order, printme);
}

现在我有一个vector<Elem>并想为向量中的每个Elem打印一个整数成员Elem.a 我可以通过创建一个新的vector<int> (为所有Elems复制a )并将其传递给打印函数来进行此操作-但是,我觉得必须存在一种传递“虚拟”矢量的方法,当operator[]用于它,仅返回成员a 请注意,我不想更改print_in_order函数来访问该成员,它应该保持常规。

这可能吗,也许带有lambda表达式? 完整代码如下。

#include <iostream>
#include <vector>

struct Elem {
    int a,b;
    Elem(int a, int b) : a(a),b(b) {}
};

template <typename vectorlike>
void print_in_order(std::vector<int> const & order,
                    vectorlike const & printme) {
    for (int i : order)
        std::cout << printme[i] << std::endl;
}

int main() {
    std::vector<Elem> printme = {Elem(1,100), Elem(2,200), Elem(3,300)};
    std::vector<int> order = {2,0,1};

    // how to do this?
    virtual_vector X(printme) // behaves like a std::vector<Elem.a>
    print_in_order(order, X);
}

直接做您想做的事实际上是不可能的。 相反,您可能想从标准算法库中获取提示,例如std::for_each ,在其中您要接受一个额外的参数,该参数是针对每个元素调用的类似于函数的对象。 然后,您可以轻松地传递仅打印所需元素的lambda函数。

也许像

template<typename vectorlike, typename functionlike>
void print_in_order(std::vector<int> const & order,
                vectorlike const & printme,
                functionlike func) {
    for (int i : order)
        func(printme[i]);
}

然后像这样称呼它

print_in_order(order, printme, [](Elem const& elem) {
    std::cout << elem.a;
});

由于C ++具有函数重载,因此您仍然可以保留旧的print_in_order函数用于纯矢量。

您可以选择两种数据结构

struct Employee
{
  std::string name;
  double salary;
  long payrollid;
};

std::vector<Employee> employees;

或者

struct Employees
{
   std::vector<std::string> names;
   std::vector<double> salaries;
   std::vector<long> payrollids;
};

C ++在设计时将第一个选项作为默认选项。 其他语言(例如Javascript)倾向于鼓励第二种选择。

如果要查找平均工资,则选择2更方便。 如果要按薪水对员工进行排序,则选项1更易于使用。

但是,您可以使用lamda在两者之间进行部分互转换。 lambda是一个琐碎的小函数,它需要一个Employee并为他返回薪水-因此,有效地提供一个双精度的平面向量,我们可以取平均值-或取一个索引和一个Employees并返回一个雇员,做一点点琐碎的数据重新格式化。

使用成员指针,可以实现代理类型,该代理类型将允许您通过用对象的一个​​成员(请参见指向data member的指针 )或其吸气剂之一(请参见指向member function的指针 )替换每个对象来查看对象的容器。 第一个解决方案仅解决数据成员问题,第二个解决方案针对这两个问题。

容器将必然需要知道使用哪个容器以及要映射哪个成员,这将在施工时提供。 指向成员的指针的类型取决于该成员的类型,因此必须将其视为附加的模板参数。

template<class Container, class MemberPtr>
class virtual_vector
{
public:
    virtual_vector(const Container & p_container, MemberPtr p_member_ptr) :
        m_container(&p_container),
        m_member(p_member_ptr) 
    {}

private:
    const Container * m_container;
    MemberPtr m_member;
};

接下来,实现operator[]运算符,因为您提到过这就是您想要访问元素的方式。 首先,用于取消引用成员指针的语法可能令人惊讶。

template<class Container, class MemberPtr>
class virtual_vector
{
public:
    virtual_vector(const Container & p_container, MemberPtr p_member_ptr) :
        m_container(&p_container),
        m_member(p_member_ptr) 
    {}

    // Dispatch to the right get method
    auto operator[](const size_t p_index) const 
    {
        return (*m_container)[p_index].*m_member;
    }

private:
    const Container * m_container;
    MemberPtr m_member;
};

要使用此实现,您将编写如下内容:

int main() {
    std::vector<Elem> printme = { Elem(1,100), Elem(2,200), Elem(3,300) };
    std::vector<int> order = { 2,0,1 };

    virtual_vector<decltype(printme), decltype(&Elem::a)> X(printme, &Elem::a);
    print_in_order(order, X);
}

这有点麻烦,因为没有模板自变量的推论发生。 因此,让我们添加一个免费函数来推断模板参数。

template<class Container, class MemberPtr>
virtual_vector<Container, MemberPtr>
make_virtual_vector(const Container & p_container, MemberPtr p_member_ptr) 
{
    return{ p_container, p_member_ptr };
}

用法变为:

int main() {
    std::vector<Elem> printme = { Elem(1,100), Elem(2,200), Elem(3,300) };
    std::vector<int> order = { 2,0,1 };

    auto X = make_virtual_vector(printme, &Elem::a);
    print_in_order(order, X);
}

如果要支持成员函数,则要复杂一些。 首先,取消引用数据成员指针的语法与调用函数成员指针略有不同。 您必须实现两种版本的operator[]并根据成员指针类型启用正确的版本。 幸运的是,该标准提供了std::enable_ifstd::is_member_function_pointer (均在<type_trait>标头中),使我们能够做到这一点。 成员函数指针要求您指定要传递给函数的参数(在本例中为非),并在表达式周围加上一组括号,以评估要调用的函数(在参数列表之前的所有内容)。

template<class Container, class MemberPtr>
class virtual_vector
{
public:
    virtual_vector(const Container & p_container, MemberPtr p_member_ptr) :
        m_container(&p_container),
        m_member(p_member_ptr) 
    {}

    // For mapping to a method
    template<class T = MemberPtr>
    auto operator[](std::enable_if_t<std::is_member_function_pointer<T>::value == true, const size_t> p_index) const
    {
        return ((*m_container)[p_index].*m_member)();
    }

    // For mapping to a member
    template<class T = MemberPtr>
    auto operator[](std::enable_if_t<std::is_member_function_pointer<T>::value == false, const size_t> p_index) const
    {
        return (*m_container)[p_index].*m_member;
    }

private:
    const Container * m_container;
    MemberPtr m_member;
};

为了测试这一点,出于说明目的,我在Elem类中添加了一个吸气剂。

struct Elem {
    int a, b;
    int foo() const { return a; }
    Elem(int a, int b) : a(a), b(b) {}
};

这是它的用法:

int main() {
    std::vector<Elem> printme = { Elem(1,100), Elem(2,200), Elem(3,300) };
    std::vector<int> order = { 2,0,1 };

    {   // print member
        auto X = make_virtual_vector(printme, &Elem::a);
        print_in_order(order, X);
    }
    {   // print method
        auto X = make_virtual_vector(printme, &Elem::foo);
        print_in_order(order, X);
    }
}
template<class F>
struct index_fake_t{
  F f;
  decltype(auto) operator[](std::size_t i)const{
    return f(i);
  }
};
template<class F>
index_fake_t<F> index_fake( F f ){
  return{std::move(f)};
} 
template<class F>
auto reindexer(F f){
  return [f=std::move(f)](auto&& v)mutable{
    return index_fake([f=std::move(f),&v](auto i)->decltype(auto){
      return v[f(i)];
    });
  };
}
template<class F>
auto indexer_mapper(F f){
  return [f=std::move(f)](auto&& v)mutable{
    return index_fake([f=std::move(f),&v](auto i)->decltype(auto){
      return f(v[i]);
    });
  };
}

现在,打印顺序可以重写为:

template <typename vectorlike>
void print(vectorlike const & printme) {
  for (auto&& x:printme)
    std::cout << x << std::endl;
}
template <typename vectorlike>
void print_in_order(std::vector<int> const& reorder, vectorlike const & printme) {
  print(reindexer([&](auto i){return reorder[i];})(printme));
}

并将.a打印为:

print_in_order( reorder, indexer_mapper([](auto&&x){return x.a;})(printme) );

可能会有一些错别字。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM