【问题标题】:c++11 combining std::tuple and std::tie for efficient orderingc++11结合std::tuple和std::tie实现高效排序
【发布时间】:2014-07-01 19:58:27
【问题描述】:

我对 std::tuple 和 std::tie 还很陌生。我需要一种根据从左到右的比较顺序有效地对结构进行排序的方法。出于这个原因,我选择使用std::make_tuplestd::tie 类型在提供的live example 中自定义订购一个StructA。元组方法提供了从左到右的内置等价比较,这对于带有 lambda 比较器的 std::sort 的 LessThanComparable 元素排序非常理想(为此我展示了 3 个示例)。

问题是,据我所知,std::make_tuple 使 inefficient copies 成为元组元素,我想知道是否有某种方法可以将 std::make_tuple 与 std::tie 结合起来,就像我尝试的那样用我的第三个比较器做 - 它失败了(否则它的输出看起来像第一个输出排序)。

在我的具体示例中,我不能直接使用 std::tie,因为我需要使用临时元素作为元组中的第一个元素。

输出如下

Order[mPath.filename(), elem1, intVal]
======================================
"/zoo/dir1/filename.txt" - nameA1 - 1
"/tmp/dir1/filename.txt" - nameA1 - 3
"/fad/dir1/filename.txt" - nameA1 - 4

Order[mPath, elem1, intVal]
======================================
"/fad/dir1/filename.txt" - nameA1 - 4
"/tmp/dir1/filename.txt" - nameA1 - 3
"/zoo/dir1/filename.txt" - nameA1 - 1

Order[mPath.filename(), elem1, intVal]
======================================
"/fad/dir1/filename.txt" - nameA1 - 4
"/tmp/dir1/filename.txt" - nameA1 - 3
"/zoo/dir1/filename.txt" - nameA1 - 1

我期待第三组输出与第一组相同,或者如果有人能告诉我如何正确混合低效的 std::tuples 和高效的 std::ties

#include <iostream>
#include <string>
#include <vector>
#include <tuple>
#include <boost/filesystem.hpp>

struct StructA {
    boost::filesystem::path mPath;
    std::string elem1;
    int intVal;
};

template<typename CharT, typename Traits>
std::basic_ostream<CharT, Traits>& 
operator<<(std::basic_ostream<CharT, Traits>& os, StructA const& sa) {
    return os << sa.mPath << " - " << sa.elem1 << " - " << sa.intVal << std::endl;
}


int main()
{
    std::vector<StructA> aStructs = {
        {"/zoo/dir1/filename.txt", "nameA1", 1}, 
        {"/fad/dir1/filename.txt", "nameA1", 4}, 
        {"/tmp/dir1/filename.txt", "nameA1", 3}
    };    

    std::cout << "Order[mPath.filename(), elem1, intVal]" << std::endl;
    std::cout << "======================================" << std::endl;
    std::sort(aStructs.begin(), aStructs.end(),
        [](const StructA& lhs, const StructA& rhs){
            return std::make_tuple(lhs.mPath.filename(), lhs.elem1, lhs.intVal) < 
                std::make_tuple(rhs.mPath.filename(), rhs.elem1, rhs.intVal);
        });

    // print reordered structs
    std::copy(aStructs.begin(), aStructs.end(),
        std::ostream_iterator<StructA>(std::cout, ""));        

    std::cout << std::endl;

    std::cout << "Order[mPath, elem1, intVal]" << std::endl;
    std::cout << "======================================" << std::endl;
    std::sort(aStructs.begin(), aStructs.end(),
        [](const StructA& lhs, const StructA& rhs){
            return std::tie(lhs.mPath, lhs.elem1, lhs.intVal) < 
                std::tie(rhs.mPath, rhs.elem1, rhs.intVal);
        });

    // print reordered structs
    std::copy(aStructs.begin(), aStructs.end(),
        std::ostream_iterator<StructA>(std::cout, ""));

    std::cout << std::endl;

    std::cout << "Order[mPath.filename(), elem1, intVal]" << std::endl;
    std::cout << "======================================" << std::endl;
    std::sort(aStructs.begin(), aStructs.end(),
        [](const StructA& lhs, const StructA& rhs){
            // attempt at efficiency - but not quite right
            return lhs.mPath.filename() < rhs.mPath.filename() && 
                std::tie(lhs.elem1, lhs.intVal) < std::tie(rhs.elem1, rhs.intVal);
        });

    // print reordered structs
    std::copy(aStructs.begin(), aStructs.end(),
        std::ostream_iterator<StructA>(std::cout, ""));
}

【问题讨论】:

  • 您仍然可以使用 std::tie 和一些 as_lvalue,例如stackoverflow.com/q/23566857/420683
  • 将来自filename() 调用的返回值分配给局部变量,然后使用std::tie。不是一个非常可扩展的解决方案,但当您只处理 2 个此类变量时也不错。
  • @johnco3 我的意思是auto f1 = lhs.mPath.filename(); auto f2 = rhs.mPath.filename(); return std::tie(f1, lhs.elem1, lhs.intVal) &lt; std::tie(f2, rhs.elem1, rhs.intVal);
  • @Praetorian - 太棒了 - 这就是我正在寻找的答案!干得好!

标签: c++ sorting c++11 stl boost-tuples


【解决方案1】:
std::tuple<std::string, std::string const&, int>
sort_helper(StructA const& s){
  return{ s.mPath.filename(), s.elem1, s.intVal };
}

然后:

std::sort(aStructs.begin(), aStructs.end(),
  [](const StructA& lhs, const StructA& rhs){
    return sort_helper(lhs)<sort_helper(rhs);
  }
);

哪个看起来更干净?

基本上以std::string 移动一次为代价。

【讨论】:

    【解决方案2】:

    我刚刚发现第三个 lambda 的问题在于我没有正确比较元素以进行等价和字典比较。正确的方法在符号 3) 中为tuple comparison 概述,它表明我应该使用以下方法。

    3) (bool)(std::get(lhs) <:get>(rhs)) || (!(bool)(std::get(rhs) (lhs)) && lhstail

    固定的 lambda 比较器根据 filename() 临时排序,然后对元组中的其他元素使用高效的 std::tie

    std::cout << "Order[mPath.filename(), elem1, intVal]" << std::endl;
    std::cout << "======================================" << std::endl;
    std::sort(aStructs.begin(), aStructs.end(),
        [](const StructA& lhs, const StructA& rhs){
            // attempt at efficiency - but not quite right
            // AHA, I think I figured it out - see tuple operator_cmp
            // return value documentation which states 
            // (bool)(std::get<0>(lhs) < std::get<0>(rhs)) || 
            // (!(bool)(std::get<0>(rhs) < std::get<0>(lhs)) && 
            // lhstail < rhstail), where lhstail is lhs without 
            // its first element, and rhstail is rhs without its 
            // first element. For two empty tuples, returns false.
            // --------------------------------------------------------
            // edit thanks to @Praetorian for suggesting the following:
            // --------------------------------------------------------
            auto f1 = lhs.mPath.filename(); auto f2 = rhs.mPath.filename();            
            return std::tie(f1, lhs.elem1, lhs.intVal) < std::tie(f2, rhs.elem1, rhs.intVal);
        });
    

    这样做使第一组结果与第三组结果相同 - 对于 filename() 临时文件来说效率并不高,但至少我不会对结构中的所有元素进行 std::make_tuple 命中。更新后的实例是here

    【讨论】:

    • 您是否真的分析了优化的构建?我认为一个好的编译器应该能够优化掉 make_tuple() 版本中的副本,并且根据这个答案上的 cmets 似乎是这种情况:stackoverflow.com/a/6219150/139091
    • 不,我从来没有机会测试性能差异,原始问题中的一个链接表明需要额外的副本来放置 make_tuple,也就是说我在 Visual C++ 2013 中看到的实现如下所示,因此看起来可以完美转发临时性 // FUNCTION make_tuple template inline tuple::type...> make_tuple(_Types&&... _Args) { // make来自元素的元组 typedef tuple::type...> _Ttype; return (_Ttype(_STD forward<_types>(_Args)...)); }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-23
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2017-04-16
    • 1970-01-01
    • 2019-06-15
    相关资源
    最近更新 更多