1

While writing a custom reflection library I encountered a strange compiler behavior. However I was able to reproduce the problem with a much simplified code. Here is:

#include <iostream>

class OtherBase{};

class Base{};

/* Used only as a test class to verify if the reflection API works properly*/
class Derived : Base, OtherBase
{
public:
    
    void Printer()
    {
        std::cout << "Derived::Printer() has been called" << std::endl;
    }
};

/*Descriptor class that basically incapsulate the address of Derived::Printer method*/
struct ClassDescriptor
{
    using type = Derived;

    struct FuncDescriptor
    {
        static constexpr const auto member_address{ &type::Printer };
    };
};

int main()
{
    Derived derived;
    auto address{ &Derived::Printer };
    (derived.*address)(); // -> OK it compiles fine using the local variable address
    (derived.*ClassDescriptor::FuncDescriptor::member_address)(); // -> BROKEN using the address from the descriptor class cause fatal error C1001 !
}

While trying debugging this problem I noticed that:

  1. It happen only if Derived has multiple inheritance.
  2. If I swap static constexpr const auto member_address{ &type::Printer } with inline static const auto member_address{ &type::Printer } it works.

Is it just a compiler bug, or I'm doing something wrong ? Can I solve this problem while keeping the constexpr ?

Please note that I'm using MSVC 2017 with the compiler version 19.16.27024.1 All compiler options are default except for /std:c++17 enabled.

I know that updating (and surely i'll do it) the compiler version to the last one will probably solve the issue, but for now I would like to understand more about this problem.

  • 1
    The code compiles with no issues using VS 2019. It also compiles with no issues using g++ 5.x and higher. – PaulMcKenzie Jul 24 '20 at 15:44
  • 1
    An internal compiler error is always an compiler error... But it has been fixed in newer versions... Your code should compile – Bernd Jul 24 '20 at 16:15
  • @PaulMcKenzie Thanks for your answer. So I think I should assume that I just spot a compiler bug and that my code is fine. – Antonio Cantarella Jul 25 '20 at 14:53
  • It looks like it. The problem did exist in VS, but doesn't appear in the latest version, so the assumption is that it was a compiler bug. – PaulMcKenzie Jul 25 '20 at 16:27

1 Answers1

0
  1. About C1001, Microsoft Developer Network suggests that you remove some optimizations in your code: Fatal Error C1001. Once you've worked out which optimization is causing the issue, you can use a #pragma to disable that optimization in just that area:

    // Disable the optimization #pragma optimize( "", off ) ... // Re-enable any previous optimization #pragma optimize( "", on )

Also, a fix for this issue has been released by Microsoft. You could install the most recent release.

  1. const and constexpr:

const declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization.

constexpr declares an object as fit for use in what the Standard calls constant expressions. But note that constexpr is not the only way to do this.

When applied to functions the basic difference is this:

const can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members.

constexpr can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly:

The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single return statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default and = delete are allowed, too, though.) As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: asm declaration, a goto statement, a statement with a label other than case and default, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed. The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)

When can I / should I use both, const and constexpr together?

A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr implies const.

constexpr const int N = 5;
is the same as

constexpr int N = 5;

However, note that there may be situations when the keywords each refer to different parts of the declaration:

static constexpr int N = 3;

int main()
{
  constexpr const int *NP = &N;
}

Here, NP is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr and const are required: constexpr always refers to the expression being declared (here NP), while const refers to int (it declares a pointer-to-const). Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N is in-fact a pointer-to-constant).

B. In member function declarations. In C++11, constexpr implies const, while in C++14 and C++17 that is not the case. A member function declared under C++11 as

constexpr void f();
needs to be declared as

constexpr void f() const;

under C++14 in order to still be usable as a const function.

You could refer to this link for more details.

Barrnet Chou
  • 1,434
  • 1
  • 2
  • 7