Variable not constexpr even though initialization condition passes static_assert

173 Views Asked by At

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?

1

There are 1 best solutions below

3
On

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.