11

What's the most efficient way of iterating over one of several known ranges based on some condition?

pseudo-code for a binary condition:

for element in (condition ? range_a : range_b)
  // do work

This 'example' shows my intention using a range-based for loop but as std::initializer_list has reference semantics it won't work.

constexpr auto some_range(bool c) -> std::initializer_list<int> {
  if (c) {
    return {1,2};
  } else {
    return {3, 4, 5};
  }
}

bool cond = true; // false

for(auto x : some_range(cond)) {
  // things
}

yields: warning: returning address of local temporary object [-Wreturn-stack-address]

During run-time I could return a std::vector but that would involve constructing a new vector every call:

auto some_range(bool c) -> std::vector<int> {
  if (c) {
    return {1,2};
  } else {
    return {3, 4, 5};
  }
}

I could use a fixed size std::array of std::optional<int> but I have to resort to a C++14 or c++11 solution.

Tom
  • 3,281
  • 26
  • 33
  • Can you resort to a C++17 solution? – Bathsheba Aug 27 '19 at 08:25
  • 3
    or `std::span` from c++20? – Jarod42 Aug 27 '19 at 08:31
  • The ranges you intend to iterate over are definitely created from within `some_range`? Or would they exist in the scope of the calling code? – lubgr Aug 27 '19 at 08:32
  • @Bathsheba @Jarod42 The code-base I am working on supports up to C++14. @lubgr Yes, that is my intention. I would like to encapsulate the data in the `some_range` function. In practice, `some_range` is a member function of a class that is responsible for providing the range. – Tom Aug 27 '19 at 08:34
  • 1
    Pity. Looks like a *constexpr if* type construction to me: and unlike some things out of later standards, you can't "borrow" the code from that later standard. Nice question though. – Bathsheba Aug 27 '19 at 08:39
  • When are the values for the ranges known? Are they, say, multiple member variables of the class in question? – Davis Herring Aug 29 '19 at 02:10
  • @DavisHerring they're known from the function's context, when writing the function. So they could be stored as a member variable of a containing class. However I would rather 'hide' these values from the rest of the class by wrapping it in the function-to-be-written. – Tom Aug 29 '19 at 06:26
  • @Tom: Maybe I’m not following, but could they be static local variables? – Davis Herring Aug 29 '19 at 06:36
  • @DavisHerring There are some costs involved in managing static variables https://www.youtube.com/watch?v=B3WWsKFePiM, consequently, the compiler will not be able to optimize the range-based for loop. Check this demo with optimizer output: https://godbolt.org/z/v6RgFH – Tom Aug 29 '19 at 07:01
  • 1
    @Tom is [this](https://godbolt.org/z/uqo7sj) what you need ? – Piotr Skotnicki Aug 29 '19 at 10:45
  • @PiotrSkotnicki Great suggestion... I ended up implementing an `array_view` that takes some `std::array`... I've updated the Q to reflect this. – Tom Aug 29 '19 at 12:08
  • 1
    @Tom You're solution has the same pitfall as the original one, the `std::array` will not outlive `conditional_range()` so you'll have a view to an array that does not exists anymore. – Holt Aug 29 '19 at 12:15
  • Hold ur horses! @Holt The array now has static storage duration so it will exist, right? – Tom Aug 29 '19 at 12:17
  • 2
    @Tom In Piotr's solution yes, not in yours. – Holt Aug 29 '19 at 12:18
  • I see, you are right, thanks for warning! – Tom Aug 29 '19 at 12:20
  • 1
    It looks like you found what I meant by static locals, but it also looks like you’re supposed to have written an answer, not edited the question. – Davis Herring Aug 29 '19 at 13:19
  • @DavisHerring Right you are, but there was the `view` involved to get it working correctly. Regarding the answer... I rather not hijack your efforts in finding a solution but allow anyone to provide a short answer instead. – Tom Aug 29 '19 at 13:22
  • It's still better to post your answer as an answer - you can still accept a different answer, and it leaves the question less cluttered. – Useless Aug 29 '19 at 14:21
  • Thanks for your advice. – Tom Aug 29 '19 at 14:36

1 Answers1

1

A range-based for loop can iterate over any expression e, whose class type has e.begin() and e.end() member functions, or non-member functions begin(e) and end(e) can be found via ADL. A simple iterable view can be therefore:

#include <cstddef>

template <typename T>
struct view
{
    T* p;
    std::size_t s;
    constexpr T* begin() const { return p; }
    constexpr T* end() const { return p + s; }
};

and then returned holding a pointer to an array with, e.g., static storage duration:

inline view<const int> conditional_range(bool a)
{
    static int ra[] = { 1, 2 };
    static int rb[] = { 3, 4, 5 };
    if (a) return { ra, 2 };
    else return { rb, 3 };
}

DEMO

This is similar to what offers with std::span.


An std::initilizer_list<T> wraps a local array, constructed automatically from the brace-enclosed initializer, and so is not usable as a return type, because in such a case the pointers it stores are invalidated on function exit.

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