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++.

15
  • Perhaps wrapping it in a custom class? Also look at this stackoverflow.com/questions/13816850/… Commented Dec 20, 2017 at 16:31
  • @JakeFreeman but this would make the code more cumbersome than the method used in func1. Commented Dec 20, 2017 at 16:34
  • 3
    Apart from the curiosity factor, is there anything to be gained by the second approach? Commented Dec 20, 2017 at 16:34
  • 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. Commented Dec 20, 2017 at 17:11
  • 1
    @MichaelWalz you can often use a call to make_array or a similar function if you don't want to bother with the size. Commented Dec 21, 2017 at 0:04

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Huh, I had no idea that static could be included here (never had any reason to try it). Does the if version gain anything over just opening a new block scope?
@underscore_d - Nothing. It's just a test of the grammar production. Demonstrating that an init statement of a static object is valid.
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);
}

3 Comments

Yepp. Compiles and runs: ideone but it's still looking scary. (I'm impressed - didn't get the trick with operator().)
Out of curiosity: Why does it work with operator() but does not even compile without?
@Scheff - With a function call, it invokes the lambda and gets the vector to iterate over. Without the call, it tries to iterate over the lambda object itself.
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.

Comments

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

Comments

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

1 Comment

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&.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.