glades
glades

Reputation: 4854

Variable not constexpr even though initialization condition passes static_assert

I'm trying to write some compile time string composing functionality. The following code compiles if you comment out the code section at the end.

godbolt

#include <iostream>

constexpr const char a[] = "hello ";
constexpr const char b[] = "dear ";
constexpr const char c[] = "world!";

constexpr size_t size(const char* s)
{
    int i = 0;
    while(*s!=0) {
        ++i;
        ++s;
    }
    return i;
}

template <typename... Is, typename = std::enable_if_t<(... && std::is_same_v<const char*, Is>)>>
constexpr size_t calc_size(Is... values) {
    return (0 + ... + size(values));
}

constexpr bool strings_equal(char const * a, char const * b) {
    return *a == *b && (*a == '\0' || strings_equal(a + 1, b + 1));
}

template <const char*... S>
class cxpr_string
{
public:
    constexpr cxpr_string() : buf_{}, size_{0} {
        int i=0;
        ( [&]() {
            const size_t max = size(S);
            for (int i=0; i < max; ++i) {
                buf_[size_++] = S[i];
            }
        }(), ...);
        buf_[size_++] = 0;
    }

    constexpr const char* get() const {
        return buf_;
    }
private:
    char buf_[calc_size(S...)+1] = { 0 };
    size_t size_;
};

template <const char*... ptr>
constexpr auto joined = cxpr_string<ptr...>().get();

int main()
{
    static_assert(strings_equal(cxpr_string<a,b,c>().get(), "hello dear world!"));
    
    std::cout << joined<a,b,c> << std::endl; // <-- Why not constexpr?? (comment this out)
    std::cout << cxpr_string<a, b, c>().get() << std::endl;
}

But if you don't you get the following error:

<source>: In instantiation of 'constexpr const char* const joined<(& a), (& b), (& c)>':
<source>:56:18:   required from here
<source>:50:16: error: '(const char*)(&<anonymous>.cxpr_string<(& a), (& b), (& c)>::buf_)' is not a constant expression
   50 | constexpr auto joined = cxpr_string<ptr...>().get();
      |                ^~~~~~

Which is very surprising to me as the exact same application of the cxpr_string class as was used to initialize the variable in question passed the static_assert! Also there are compiler differences: It doesn't compile in gcc 12.1 and clang 14.0.0, but it compiles (but fails) in msvc 19.31.

What am I missing?

Upvotes: 1

Views: 183

Answers (1)

n. m. could be an AI
n. m. could be an AI

Reputation: 120079

Here is a kinder, gentler reproduction of the problem.

Clang tells you what the problem is, sort of. "Pointer to subobject of temporary is not a constant expression". Indeed, where do you want joined to point at?

A constexpr variable must be initialised by a constant expression, which foo().get() is not. However it is a core constant expression. Indeed:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine (6.9.1), would evaluate one of the following:

  • [...]
  • an lvalue-to-rvalue conversion (7.3.1) unless it is applied to
    • [...]
    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E

However

A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:

  • [...]
  • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (7.6.6), the address of a non-immediate function, or a null pointer value

I guess one can pass a core constant expression to a constexpr function, and the result might be (but not always is) a full honest constant expression, suitable for initialising a constexpr variable.

Upvotes: 1

Related Questions