c++vectorc++17function-pointersstdvector

Workaround GCC bug to create an immediately-accessed automatically-typed vector literal of function pointers


Problem

A known bug in GCC <12 precludes initialising and immediately accessing implicitly typed vectors like so:

auto elem = (std::vector {1, 2, 3})[0];

which for some types, can be worked around using decltype:

auto elem = (std::vector <decltype(1)> {1, 2, 3})[0];

Alas this workaround does not work for function pointers:

void f() { }

// won't compile in Clang nor GCC for unknown (by me) reason
auto elem = (std::vector <decltype(f)> {f,f,f})[0];

// won't compile in GCC due to known bug
auto elem = (std::vector {f, f, f})[0];

Why does this workaround not work for function pointers, despite it working for other types? Is there another workaround such that a vector of function pointers can be initialised and immediately accessed without explicitly declaring the pointer type (i.e. the function signature)?

Further context

Consider creating a vector of elements without explicitly declaring the element type:

auto vec = std::vector{1, 2, 3};

This compiles fine with Clang and GCC (with -std=c++17). I wish however to avoid assigning the vector to a variable, and instead fetch an element in the same line:

auto elem = (std::vector{1, 2, 3}) [0];

This compiles fine in Clang, but fails in GCC with exception

error: missing template arguments after ‘vector<...auto...>’
      |     auto elem = (std::vector{1, 2, 3})[0];
      |                 ^~~~~~
In file included from /usr/include/c++/11/vector:67,
/usr/include/c++/11/bits/stl_vector.h:389:11: note: ‘template<class _Tp, class _Alloc> class std::vector’ declared here
      |     class vector : protected _Vector_base<_Tp, _Alloc>
      |           ^~~~~~

This is an identical error to that triggered when compiling my original example in a standard earlier than C++17. It is as if GCC fails to recognise the implicit-type syntax if it is a sub-expression. Is this a bug in GCC?

In any case, I can remedy it using decltype:

auto elem = (std::vector <decltype(0)> {1, 2, 3}) [0];

which compiles in both Clang and GCC.

Alas, I do not actually wish to store integers, but function pointers! The below code compiles fine in Clang but fails in GCC, as we saw occur with integers.

void f() {}

auto elem = (std::vector {f, f, f}) [0];

Alas trying to use decltype, which worked for integers, does not work for function pointers! Expression

(std::vector <decltype(f)> {f, f, f}) [0]

will fail to compile in both Clang and GNU, with errors:

In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream:43:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/ios:222:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__locale:15:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/shared_ptr.h:23:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/allocator.h:151:19: error: multiple overloads of 'address' instantiate to the same signature 'const_pointer (const_reference) const noexcept' (aka 'void (*(void (&)()) const noexcept)()')
    const_pointer address(const_reference __x) const _NOEXCEPT {
                  ^
/usr/include/c++/11/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<void()>’:
/usr/include/c++/11/bits/allocator.h:124:11:   required from ‘class std::allocator<void()>’
/usr/include/c++/11/bits/stl_vector.h:87:21:   required from ‘struct std::_Vector_base<void(), std::allocator<void()> >’
/usr/include/c++/11/bits/stl_vector.h:389:11:   required from ‘class std::vector<void()>’
test.cpp:25:48:   required from here
/usr/include/c++/11/ext/new_allocator.h:96:7: error: ‘const _Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::const_reference) const [with _Tp = void(); __gnu_cxx::new_allocator<_Tp>::const_pointer = void (*)(); __gnu_cxx::new_allocator<_Tp>::const_reference = void (&)()]’ cannot be overloaded with ‘_Tp* __gnu_cxx::new_allocator<_Tp>::address(__gnu_cxx::new_allocator<_Tp>::reference) const [with _Tp = void(); __gnu_cxx::new_allocator<_Tp>::pointer = void (*)(); __gnu_cxx::new_allocator<_Tp>::reference = void (&)()]’
   96 |       address(const_reference __x) const _GLIBCXX_NOEXCEPT
      |       ^~~~~~~

How can I initialise and access this inline temporary vector without explicitly specifying the type, which would of course encode the function signature and return type?

Why am I doing this?

I have multiple single-integer-templated functions with distinct signatures and return-types:

template <int N> void  f(int x)        { ... }
template <int N> float g(int x, int y) { ... }
...

The template parameter informs compile-time optimisations in the functions (for more info, see this SO post). A simple way to choose the template parameter of the invoked function is to build an array of pointers to the function with all legal template parameters:

std::vector <void(*)(void)> f_funcs { f<0>, f<1>, f<2>, ... };

int param = 2;
f_funcs[param]();

Notice I explicitly specified the type void(*)(void) (the signature of f) as the vector template. I would have to hardcode the array initialisation for every one of my distinctly-typed functions:

std::vector <float(*)(int,int)> g_funcs { g<0>, g<1>, g<2>, ... };

int param = 2;
float ret = g_funcs[param](6,9);

This compiles fine, but is tedious boilerplate. I wish to avoid hardcoding an array literal of function pointers for each of my functions, especially because some of them accept two template parameters and need a 2D initialiser. So, I wrote a macro:

#define GET_FUNC(func, param) \
    (vector {func<0>, func<1>, func<2>, ...} ) [param]

int param = 3;
auto f_ = GET_FUNC(f, param);
auto g_ = GET_FUNC(g, param);

f_();
float x = g_(6,9);

Use of implicit typing avoids me having to pass the signatures of f and g to the macro. This compiles fine in Clang, but as described above, does not compile in GCC.

Introducing decltype...

#define GET_FUNC(func, param) \
    (vector <decltype(func<0>)> {func<0>, func<1>, func<2>, ...}) [param]

does not compile in Clang nor GCC.


Solution

  • As commented by Jarod42, a workaround to support versions of GCC earlier than 12 is to maintain an array of explicit pointers:

    (std::vector <decltype(&f)> {&f, &f, &f}) [0]
    

    Despite the comments, this is not the (inapplicable) solution presented in this related but simpler problem. This question is not a duplicate (gee wizz)!

    The solution presented by user12002570 does not work for the templated functions described in my question (godbolt).

    // fails to compile in GCC < v12
    auto func = ((vector {f<0>, f<1>, f<2>}))[0];