2017-07-06 82 views
-1

我有一个需要向量化输入的函数。为简单起见,让我们用这个print_in_order功能:几乎将struct的向量转换为struct成员的向量

#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.a,每个ELEM。我可以通过创建一个新的vector<int>(复制所有元素的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); 
} 
+3

写自己的类,它包装一个向量和重载'[]'运营商? – imreal

回答

0

使用成员指针可以实现代理类型,通过它可以查看一个对象的容器,通过它的一个成员(请参阅pointer to data member)或其中一个获得者(请参阅pointer to 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; 
}; 

为了测试这一点,我添加了一个getter到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); 
    } 
} 
+0

谢谢,这就是我一直在寻找的东西。我也意识到似乎没有单线解决方案。 – sssascha

3

根本不可能直接做你想做的事。相反,您可能需要从standard algorithm library中获取提示,例如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功能对于普通的载体。现在

+2

您可以使用成员指针直接执行此操作。 –

+2

既然你的函数是完全一般的,可能不应该再叫'print_in_order',也许'for_each_in_order'或'apply_in_order'。 –

+0

你可以重新安排它仍然是'print_in_order':'[](Elem const&elem) - > Elem :: a const&{return elem.a; }'和'for(int i:order)std :: cout << func(printme [i]);' – Caleth

-1
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)); 

可能会有一些错别字。

0

你有两个数据结构

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更容易处理。

但是,您可以使用lamdas在两者之间进行部分相互转换。 lambda是一个微不足道的小函数,它接受一个Employee并为他返回一份薪水 - 这样可以有效地提供一个双倍的平面向量,我们可以采取这种方式 - 或者采用索引和Employees并返回雇员,做一点点简单的数据重新格式化。

+0

这似乎没有回答这个问题。 –