Should this be a constexpr or not?

Viewed 200

Consider this code snippet (godbolt):

#include <cstdio>
#include <string>
#include <string_view>

struct Option
{
    std::string_view name;

    constexpr Option( std::string_view const n ) noexcept : name{n} {}
};

template< std::size_t N >
class TransformedOption : public Option
{
public:
    constexpr TransformedOption( std::string_view const nameStr ) :
        Option{ { nameStorage_, N - 1 } }
    {
        for ( auto i = 0U; i < N; ++i )
        {
            if ( nameStr[ i ] == '_' ) { nameStorage_[ i ] = '-'; }
            else                       { nameStorage_[ i ] = nameStr[ i ]; }
        }
    }
    private:
        char nameStorage_[ N ] = {};
};

template< std::size_t N >
constexpr TransformedOption< N > make( char const (&nameStr)[ N ] ) noexcept
{
    return TransformedOption< N  >{ nameStr };
}

int main()
{
    /*constexpr*/ auto t = make( "abcd_efgh_ijkl_mnop_peqst" );
    std::printf( "%s\n", t.name.data() );
    return 0;
}

Basically, I want to perform compile-time string transformation by replacing each _ with - and making sure that the final binary contains only the transformed string (not the original).

I've tried Clang 10.0.1, GCC 10.2 and MSVC 19.24 (see above godbolt link). The weird stuff the following:

  • if constexpr is commented-out in main, then MSVC generates incorrect code (i.e. runtime transformation of string), but both GCC and clang generate correct code (i.e. transformed string constant is embedded into the assembly)
  • if constexpr is not commented-out in main, then MSVC generates correct code (i.e. transformed string constant is embedded into the assembly), but both GCC and clang fail to compile the code, stating that t is not initialized by constant expression (see godbolt). The weirdest thing is the GCC error message, which outputs the transformed string in its error and states that it's not a constant expression.

Well, which compiler is right, according to the C++ standard? To whom should I report a bug? To GCC and Clang folks or to Microsoft?

1 Answers

The constexpr declaration works in all compilers when t is also declared static.

constexpr static auto t = make( "abcd_efgh_ijkl_mnop_peqst" );

The reason is the string_view. It's a reference type that refers into the object being initialized. So one way or another, you are initializing a contexpr pointer. Now, a constexpr pointer (that is not initialized to a null pointer) may only be initialized with the address of an object with static storage duration.

[expr.const] (emphasis mine)

11 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 an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
  • 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 ([expr.add]), the address of a non-immediate function, or a null pointer value,
  • if the value is of pointer-to-member-function type, it does not designate an immediate function, and
  • if the value is an object of class or array type, each subobject satisfies these constraints for the value.

An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.

When you declare the object to be of automatic storage duration, the pointer in the string_view is not initialized with the address of a static object. Hence GCC and Clang rightfully complain.

The self reference is what makes this interesting and tricky.

Related