Possible bug with constexpr functions?

Viewed 128

Is the following code a correct use of constexpr functions? It essentially tries to access the static constexpr member variable _size in various ways.

It compiles without issue using g++, but fails with msvc-2017 / 2019 and clang.

Code sample available for testing via godbolt here.


It seems that this can be made to compile (everywhere) if the constexpr function is replaced by an equivalent macro-workaround. Uncomment USE_MACRO_WORKAROUND to test.

For me, this seems to suggest a compiler bug relating to constexpr functions?

(Since the macro-version works, this suggests the compiler should have enough compile-time information available to deal with the constexpr function. Obviously g++ can do this...)


(This example is just a simple contrivance. The real code is part of this library).

#include <cstddef>

//define USE_MACRO_WORKAROUND

template <size_t N = +1>
struct expansion 
{
    size_t static constexpr _size = N ;
    double                  _xdat [ N ] ;
};

#if defined(USE_MACRO_WORKAROUND)

// ugly macro-based hack that's equiv. to compile 
// time foo()...
// does compile everywhere

#define foo(_aa, _bb) _aa._size + _bb._size

#else

// why does this cause problems? works for g++ 7,
// 8, 9, but not msvc, etc

template <size_t NA, size_t NB>
inline size_t constexpr foo (
    expansion <NA> const& _aa,
    expansion <NB> const& _bb
    )
{
    return _aa._size + _bb._size;
}

#endif  //USE_MACRO_WORKAROUND

template <size_t NA, size_t NB>
inline void goo (
    expansion <NA> const& _xx,
    expansion <NB> const& _yy
    )
{   // this will not compile with msvc, reporting
    // C2131: expression did not evaluate to a constant
    expansion<foo(_xx, _yy)> _tt;
}

int main ()
{
    expansion< 2 > _x2;
    expansion< 4 > _x4;

    // this seems to work for both g++ and msvc
    expansion<foo(_x2, _x4)> _x6;

    // via msvc, this leads to the errors above
    goo (_x2, _x4) ;

    return 0;
}
1 Answers

According to cppreference.com:

Reference variables can be declared constexpr (their initializers have to be reference constant expressions):

The problem with this program is that you are trying to use the reference to runtime variables (declared at the beginning of main) in the constexpr context. Although the variable accessed inside of the objects is global state (static) and inside the constexpr context, the address of these runtime variables cannot be used in this case. I was unable to get the code you provided compiling with any modern compiler aside from GCC 9.0 and earlier. GCC trunk failed to compile.

I was able to test this using one of the best tools available for us, system level programmers, Matt Godbolt's Compiler Explorer. Simplified example

These variables, interestingly, can seemingly be used if, instead, we decide to copy them by value in the call to goo(). I was able to get this program to compile on multiple versions of each clang, gcc, and msvc by removing the ampersands from the function signature:

#include <cstddef>
#include <iostream>

template <
    size_t N = +1
         >
class expansion
{
public:
    size_t static constexpr _size = N ;

    double                  _xdat [ N ] ;
    size_t                  _xlen = 0 ;
};

template <
    size_t NA, size_t NB
         >
inline size_t constexpr foo (
    expansion <NA> const& _aa,
    expansion <NB> const& _bb
    )
{
    return _aa._size + _bb._size;
}

template <
    size_t NA, size_t NB
         >
inline void goo (
    expansion <NA> const _xx,
    expansion <NB> const _yy
    )
{
    // this will not compile with msvc, reporting
    // C2131: expression did not evaluate to a constant
    size_t
    constexpr _nn = foo(_xx, _yy);

    expansion<_nn> _tt;
}

int main ()
{
    expansion< 2 > _x2;
    expansion< 4 > _x4;

    // this seems to work for both g++ and msvc
    size_t
    constexpr _n6 = foo(_x2, _x4);

    expansion<_n6> _x6;

    // via msvc, this leads to the errors above
    goo (_x2, _x4) ;

    std::cout << _n6 << std::endl;

    return 0;
}

EDIT:

Because I was unable to affirm whether or not it is a compiler bug, (Because I was unable to reproduce the results you said you got), I hope I was able to help you use the code you provided, since I assume it is a legitimate use case you've come across.

Related