Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should this be a constexpr or not?

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?

like image 248
DoDo Avatar asked Aug 10 '20 15:08

DoDo


People also ask

Should I use constexpr or const?

const applies for variables, and prevents them from being modified in your code. constexpr tells the compiler that this expression results in a compile time constant value, so it can be used in places like array lengths, assigning to const variables, etc.

How do I know if a function is constexpr?

The easiest way to check whether a function (e.g., foo ) is constexpr is to assign its return value to a constexpr as below: constexpr auto i = foo(); if the returned value is not constexpr compilation will fail.

Why should we use constexpr?

The primary usage of constexpr is to declare intent. If an entity isn't marked as constexpr - it was never intended to be used in a constant-expression; and even if it is, we rely on the compiler to diagnose such context (because it disregards our intent).

Does constexpr improve performance?

In Conclusion. constexpr is an effective tool for ensuring compile-time evaluation of function calls, objects and variables. Compile-time evaluation of expressions often leads to more efficient code and enables the compiler to store the result in the system's ROM.


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.

like image 127
StoryTeller - Unslander Monica Avatar answered Oct 06 '22 01:10

StoryTeller - Unslander Monica