1

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?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
glades
  • 3,778
  • 1
  • 12
  • 34

1 Answers1

1

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • So basically what you're saying is that while the expression itself is constant, the lifetime of the pointed to string ends when the function ends. Which means that essentially what is happening inside the compiler is the same as at runtime. But how would I then pin down the result of the operation? – glades Jun 06 '22 at 19:01
  • @glades Not sure what you mean by "pin down". If you need a static constexpr variable of a pointer type, it better point to something static. How else could it possibly work? If you want a static constexpr string object, you have a suitable class, use it instead of a pointer to char. [demo](https://godbolt.org/z/drEen3ns5) – n. m. could be an AI Jun 06 '22 at 20:14
  • With "pin down" I meant to materialize the variable into the binary of the executable. I had a hard time wrapping my head around this, but I guess I understand it a bit better now, thx! – glades Jun 07 '22 at 15:59