Search code examples
c++constantsconstexprc++23

Can you declare static local variables in a constexpr function?


Are you allowed to use static local variables in constexpr functions? For example:

#include <string_view>
#include <utility>

enum class axis {
    x, y, z
};

constexpr std::string_view axis_name(axis a) {
    // use static constexpr to avoid putting the table onto the stack
    static constexpr std::string_view names[] {
        "x", "y", "z"
    };
    return names[std::to_underlying(a)];
}

constexpr auto x_name = axis_name(axis::x);

GCC 12 fails to compile this with the error:

<source>:9:39: error: 'names' defined 'static' in 'constexpr' context
    9 |     static constexpr std::string_view names[] {
      |                                       ^~~~~

Other compilers allow it. What are the rules, and when is it allowed?

  • can we use static in general, or
  • just static const, or
  • just static constexpr?

Solution

  • This code is okay since C++23, because the restrictions on static constexpr have been lifted.

    Relaxed Restrictions Since C++23

    Until C++23, it was not only illegal to initialize a static local in a constexpr function, it was also illegal to declare one, even if control didn't flow through it. For example:

    constexpr void foo() {
        if (false) {
            // allowed since C++23, otherwise ill-formed
            // ok because control never flows through x
            static int x;
        }
        // allowed since C++23, otherwise ill-formed
        // ok because static constexpr
        static constexpr int y = 0;
    }
    

    Disallowing static constexpr variables has always been an arbitrary restriction, lifted by P2647 - Permitting static constexpr variables in constexpr functions.

    Compiler Support

    To use this feature, you must use a recent compiler. As of the time of writing, this is the compiler support:

    C++23 Feature Paper GCC Clang MSVC
    Permitting static constexpr variables in constexpr functions P2647R1 13 16 /

    See also: C++23 compiler support page on cppreference

    Why Not Allow static In General?

    It is unclear how static objects should behave at compile time, and how this could be implemented consistently across all compilers. For example, consider:

    constexpr std::size_t get_size() {
        static std::size_t s = 0;
        // different result each time get_size() is called
        return ++s;
    }
    
    // what are A and B??? B = 1, and B = 2 are both reasonable
    template <std::size_t A = get_size(), B = get_size()>
    constexpr std::size_t foo() { return A + B; }
    

    It's easy to see static introduces a huge amount of problems and questions when used at compile time, so it will likely never be unrestricted. It breaks the assumption that constexpr functions are pure (have no side effects), and also makes it unsafe to memoize them (cache their results to call them less often).

    About static const

    static const is also problematic, because it could be initialized to a function argument:

    constexpr int remember_first_call(int x) {
        static const int y = x;
        return y;
    }
    

    This function will always return the argument that it has first been called with, which introduces an idea of "state" and "time" at compile time that shouldn't exist. However, there is one exception to the rule for static const:

    constexpr int foo() {
        // allowed since C++23
        // ok because const integers are implicitly constexpr when possible
        static const int x = 3;
        return x;
    }