6

I have code from here:

std::sort(begin(v), end(v), [](auto const &t1, auto const &t2) {
        return get<0>(t1) < get<0>(t2); // or use a custom compare function
});

I wanted to sort tuple multiple times so I wrote this code:

int k = 10;
while(k--){
    std::sort(begin(v), end(v), [](auto const &t1, auto const &t2) {
    return get<k>(t1) < get<k>(t2); // or use a custom compare function
    });
}

but I get error error: ‘k’ is not captured. I tried to do it in this way:

int k = 10;
while(k--){
    std::sort(begin(v), end(v), [&k](auto const &t1, auto const &t2) {
    return get<k>(t1) < get<k>(t2); // or use a custom compare function
    });
}

but it is not the proper way and error error: the value of ‘k’ is not usable in a constant expression occurs.

How to capture k variable?

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
Piotr Wasilewicz
  • 1,751
  • 2
  • 15
  • 26
  • 1
    You capture it by reference and that is okay, you can also capture by value by removing the `&`. Your problem is, that `k` is not a compile time constant, which is required for the template. – mch Apr 11 '18 at 07:21
  • Unrelated, but I guess you may want `std::stable_sort` instead of `std::sort`. – xskxzr Apr 11 '18 at 07:25
  • 1
    Also tangentially related, but if you want a reverse lexicographical sort, you don't need to invoke a sorting algorithm numerous times – StoryTeller - Unslander Monica Apr 11 '18 at 07:34
  • 1
    http://coliru.stacked-crooked.com/a/05ab22354dd1b570 – Piotr Skotnicki Apr 11 '18 at 07:35
  • @StoryTeller Yes, I have task to do this kind of sort in simplest and shortest way. – Piotr Wasilewicz Apr 11 '18 at 07:52
  • @PiotrSkotnicki What if I have tuples with more values? For example `std::vector>` – Piotr Wasilewicz Apr 11 '18 at 07:57
  • 1
    There is no simple solution for this, as which `operator<` needs to be called depends on the specific type of the tuple template argument. This needs to be known compile-time. I.e. you need a templated function. BTW, Why are you using `std::tuple` and not `std::array`? That would solve your issues. – JHBonarius Apr 11 '18 at 09:09

5 Answers5

5

As mch said in the comment, the problem is that k is not a compile time constant.

For a compile time constant, to iterate from N to 0, you may need template and recursion:

#include <algorithm>
#include <tuple>
#include <type_traits>
#include <vector>

using namespace std; // just for simplify, and not recommended in practice

template <size_t N, typename Iterator, enable_if_t<N == 0, int> = 0>
void foo(Iterator begin, Iterator end)
{
    sort(begin, end,
        [](const auto &t1, const auto &t2) {
            return get<0>(t1) < get<0>(t2); 
        }
    );
}

template <size_t N, typename Iterator, enable_if_t<N != 0, int> = 0>
void foo(Iterator begin, Iterator end)
{
    sort(begin, end,
        [](const auto &t1, const auto &t2) {
            return get<N>(t1) < get<N>(t2); 
        }
    );
    foo<N - 1>(begin, end);
}

int main()
{
    vector<tuple<int, int>> v{{0, 1}, {0, 0}, {1, 1}};
    foo<1>(v.begin(), v.end());

    // posible results:
    // {0, 0}, {0, 1}, {1, 1}
    // {0, 1}, {0, 0}, {1, 1} // impossible if use std::stable_sort instead
}
xskxzr
  • 12,442
  • 12
  • 37
  • 77
4

std::get only accepts a template argument which is an expression whose value that can be evaluated at compiling time. You can't use k, because it is a variable that changes value.

std::sort(begin(v), end(v), [](auto const &t1, auto const &t2) {
    const int k = 3;
    return std::get<k>(t1) < std::get<k>(t2); // or use a custom compare function
});

As I wrote in the comments, I know const int = 3 will shadow the k value outside of the lambda expression, but this example shows that get will work when it it receives a compiling time constant value. For example, if you try to set k = 5, for example where v only has 4 tuple parameters, the compiler will give an error because it knows that this is out of range.

The following code will give an error, but if k is set to 3, it will work

std::vector<std::tuple<int, int, int, int>> v;
std::sort(begin(v), end(v), [](auto const &t1, auto const &t2) {
            const int k = 5;
            return std::get<k>(t1) < std::get<k>(t2); // or use a custom compare function
        });
JHBonarius
  • 10,824
  • 3
  • 22
  • 41
Arkady Godlin
  • 588
  • 2
  • 9
1
#include <tuple>
#include <utility>
#include <cstddef>
#include <algorithm>
#include <cstddef>
#include <iterator>

template <std::size_t I, typename It, typename F>
void sort_tuple(It it, It end, F f)
{
    std::stable_sort(it, end, [f](const auto& t1, const auto& t2)
    {
        return f(std::get<I>(t1), std::get<I>(t2));
    });
}

template <typename It, typename F, std::size_t... Is>
void sort_tuple(It it, It end, F f, std::index_sequence<Is...>)
{
    int dummy[] = { 0, (sort_tuple<sizeof...(Is) - Is - 1>(it, end, f), 0)... };
    static_cast<void>(dummy);
}

template <typename It, typename F>
void sort_tuple(It it, It end, F f)
{
    sort_tuple(it, end, f, std::make_index_sequence<
            std::tuple_size<typename std::iterator_traits<It>::value_type>::value
                           >{});
}

Test:

std::vector<std::tuple<int, int, int>> v{ {2,1,2}, {2,2,2}, {3,2,1}
                                        , {1,1,1}, {1,2,1}, {2,2,1} };

sort_tuple(begin(v), end(v), [](const auto& t1, const auto& t2)
{
    return t1 < t2;
});

for (auto& t : v)
{
    std::cout << std::get<0>(t) << " " << std::get<1>(t) << " " << std::get<2>(t) << std::endl;
}

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
0

You are using k as a template argument so it must be available at compile time. One way to do this by making it a constexpr:

constexpr int k = 1;
int j = k;

while(j--){
    std::sort(begin(v), end(v), [&k](auto const &t1, auto const &t2) {
    return get<k>(t1) < get<k>(t2); // or use a custom compare function
    })
}
Paul Evans
  • 27,315
  • 3
  • 37
  • 54
0

Template non-type arguments must be compile time constants. int k=0; is not a compile time constant.

template<std::size_t...Is>
auto index_over(std::index_sequence<Is...> ={}){
  return [](auto&&f)->decltype(auto){
    return f( std::integal_constant<std::size_t,Is>{}... );
  };
}
template<std::size_t N>
auto index_upto(std::integral_constant<N> ={}){
  return index_over(std::make_index_sequence<N>{});
}

these are helpers to get efficient compile time values from 0 up to N-1.

auto foreacher=[](auto&&f){
  return [f](auto&&...args){
    using discard=int[];
    (void)discard{0,(void(
      f(decltype(args)(args))
    ),0)...};
  };
};

the above is obscure to replace short code. foreacher(f) returns a function g. g(a,b,c) does f(a) then f(b) then f(c).

Now glue it together:

auto sort_v_by_k=[&](auto k){
  std::sort(begin(v), end(v), [](auto const &t1, auto const &t2) {
    return get<k>(t1) < get<k>(t2); // or use a custom compare function
  });
};
index_upto<11>()( foreacher( [&](auto k){
  sort_v_by_k( std::integral_constant<std::size_t, 11-k >{} );
}));

and ignoring typos, done.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524