15

Sorry for the awkward title, but I couldn't find a better one.

Consider this sample code (it has no purpose except illustrating the question):

#include <vector>

void FooBar(int);

void func1()
{
    static std::vector<int> vec {1, 2, 3, 4};

    for (auto & v : vec)
      FooBar(v);
}

void func2()
{
    for (auto & v : std::vector<int> {1, 2, 3, 4})
      FooBar(v);
}

The disassembly of this can be found here

In func1 the static vec vector is supposed to be constructed once and for all at startup. Actually the disassembly on godbolt mentioned above shows that the initialisation of the static vec is done only upon the first call of func1 and not at startup, but that's not the point here.

Now consider func2: here the vector is directly declared "inline" (not sure how this is actually called) inside the for statement, but of course that vector is constructed every time func2 is called.

Is there a way to declare that vector statically and inside the for statement, like for (auto & v : static std::vector<int> { 1, 2, 3, 4}) which is unfortunately not legal C++.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • Perhaps wrapping it in a custom class? Also look at this https://stackoverflow.com/questions/13816850/is-it-possible-to-develop-static-for-loop-in-c – Jake Freeman Dec 20 '17 at 16:31
  • @JakeFreeman but this would make the code more cumbersome than the method used in `func1`. – Jabberwocky Dec 20 '17 at 16:34
  • 3
    Apart from the curiosity factor, is there anything to be gained by the second approach? – R Sahu Dec 20 '17 at 16:34
  • @RSahu not really, just code compactness and the locality of the vector which has no purpose outside the `for` loop. This is not real code, it's just a minimal example. – Jabberwocky Dec 20 '17 at 16:35
  • Something like an [anonymous automatic static variable](https://stackoverflow.com/a/391782/1460794)? – wally Dec 20 '17 at 16:36
  • @wally yes, somewhat – Jabberwocky Dec 20 '17 at 16:39
  • @Scheff That would make a nice answer. I think you just need to enclose the lambda in brackets and then invoke the () operator. – wally Dec 20 '17 at 17:00
  • Yes, I've used lambdas like that before. (This is called an IIFE, immediately executed function expression, in Javascript _et al._) – underscore_d Dec 20 '17 at 17:01
  • Tried to compile in ideone but it complained about missing `begin()`. I'm not that experienced in lambdas to see how to change it or if it's not possible at all. – Scheff's Cat Dec 20 '17 at 17:02
  • @Scheff, yes it doesn't compile and as you mentioned in your (now deleted) comment: it __is__ too scary for me, and far less readable than the method used in `func1`. – Jabberwocky Dec 20 '17 at 17:04
  • 3
    Note that using a `std::vector` here is counter-productive, because its whole purpose is runtime resizeability which is directly working against your desire to have a singleton/constant. So I assume that is a placeholder for some user class where a `constexpr` version (`std::array` or `std::initializer_list`) is not readily available. – Ben Voigt Dec 20 '17 at 17:11
  • @BenVoigt I can't use `std::array`, because I don't know the size of the array, but you're right, a `std::initializer_list` is a better alternative here as the list will never grow or shrink, but the question remains the same. – Jabberwocky Dec 20 '17 at 17:18
  • @BenVoigt forgot to mention: the elements of the vector/initializer_list are _actually_ `int`s. – Jabberwocky Dec 20 '17 at 17:48
  • 1
    @MichaelWalz you can often use a call to [`make_array`](http://en.cppreference.com/w/cpp/experimental/make_array) or a similar function if you don't want to bother with the size. – Alex Celeste Dec 21 '17 at 00:04
  • The comparison between the resulting assembly of Clang and GCC, with array or vector, is overwhelming. – Oliv Dec 21 '17 at 09:47

5 Answers5

13

This won't actually help you yet. But there's something you can do in C++2a. It is, however, based on something that exists already in C++14 and C++17.

C++2a added an init-statement to the range based for loop. That's the new bit, the old bit is that it's the same init-statement as it is defined today. And the definition is as follow ([stmt.stmt]):

init-statement:
    expression-statement
    simple-declaration

What I'm going for is that a simple-declaration may contain a static qualifier. Yup, it does what you'd expect. So in C++2a you may write:

for (static std::vector<int> vec {1, 2, 3, 4}; int v : vec) {
   // Do things.
}

And if you want to test compiler support for it today, here's a C++17 kludge:

if (static std::vector<int> vec {1, 2, 3, 4}; true) {
    // init-statement in if was added in C++17
  for(int v : vec)
    FooBar(v);
}

And its disassembly.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
9

Another option using a lambda (from the comments on the main question):

void func()
{
    for(auto & v : ([]() -> std::vector<int>& { static std::vector<int> vec{1, 2, 3, 4}; return vec; })())
        FooBar(v);
}
wally
  • 10,717
  • 5
  • 39
  • 72
3

No, in current C++ (C++17) it is not possible. A range-based for is equivalent to the following pseudo-code:

{
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
} 

There is no init-statement here. The range_expression must either be initialized a priori or you must pass in a braced-init-list to be evaluated (like std::vector<int>{1, 2, 3, 4}). This will be changed in C++20.

3

You can do better than that: you can just have everything on the stack. If you compile the following on -O2 or higher, it essentially gets unrolled into 4 calls to FooBar().

Also, looking at disassembly with optimizations off isn't very meaningful.

void func3()
{
    constexpr int vs [] = { 1, 2, 3, 4 };
    for ( int const v : vs )
        FooBar(v);
}
KevinZ
  • 3,036
  • 1
  • 18
  • 26
2

Indeed, there is nothing in the C++ standard that disallow the optimization you are looking for. The heap memory that std::vector<int,std::allocator> allocates could indeed be replaced by static memory without changing your program behavior. But as shown by your link (even if one add aggressive optimization options) compilers do not perform the expected optimization.

So you may choose to use a std::array instead of a std::vector, std::array are easyly "understood" by optimizer, here the assembly of:

void FooBar(int);

void func2()
{
  for (auto & v : std::array<int,4> {1, 2, 3, 4})
    FooBar(v);
}

As you see in the assembly, the array is stored inside static memory:

.LC0:
    .long   1
    .long   2
    .long   3
    .long   4

For fun, you can get as good assembly by using a custom allocator that use static memory, here the assembly of:

void FooBar(int i);

template<class T>
class static_alloc
  {
  static typename std::aligned_storage<4*sizeof(T),alignof(T)>::type buff;
  static bool allocated;

public:

  using value_type = T;

  bool operator==(const static_alloc&){
    return true;
  }
  bool operator!=(const static_alloc&){
    return false;
  }

  T* allocate(std::size_t n)
    {
    if (allocated) throw std::bad_alloc{};
    allocated=true;
    return reinterpret_cast<T*>(&buff);
    }
  void deallocate(T*,std::size_t)
    {
    allocated=false;
    }
  };
template<class T>
bool static_alloc<T>::allocated=false;
template<class T>
std::aligned_storage_t<4*sizeof(T),alignof(T)> static_alloc<T>::buff;


void func2()
{
   for (auto & v : std::vector<int,static_alloc<int>>{1,2,3,4})
      FooBar(v);
}
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • 1
    For the optimization in question to happen automatically, the compiler would have to recognize that the elements are never written even though the program takes a non-`const` lvalue reference to each. Not out of the question, but certainly a harder problem than the version using `auto` or `const auto&` instead of `auto&`. – Ben Voigt Dec 20 '17 at 19:07