Search code examples
c++templatesvisual-c++visual-studio-2019

MSVC can't compile this code, complains `error C2131: expression did not evaluate to a constant` without constexpr


MSVC can't compile the below code (https://godbolt.org/z/feenYcaen):

error C2131: expression did not evaluate to a constant

Line 10 comes from MSVC's own offsetof. I think this statement is a constant expression, even if it's not, can't MSVC compute this at runtime? Is this an MSVC bug?

#include <type_traits>
#include <iostream>

struct A{};

template<typename T, A T::*MPtr>
struct KKP {
    private:
   static const std::uintptr_t c =
   ((size_t) & reinterpret_cast<char const volatile&>((((T*)0)->*MPtr)));
};

struct PC {
    A t;
};

size_t f() {
    KKP<PC, &PC::t> ar;
}

In fact, I solved this by using a member function instead of a class member (https://godbolt.org/z/c65eEnvd7), but then MSVC becomes more confusing for me:

#include <type_traits>
#include <iostream>

struct A{};

template<typename T, A T::*MPtr>
struct KKP {
    private:
    static constexpr size_t c() {
       return ((::size_t) & reinterpret_cast<char const volatile&>((((T*)0)->*MPtr)));
    }
};

struct PC {
    A t;
};

size_t f() {
    KKP<PC, &PC::t> ar;
    return 0;
}

Solution

  • offsetof cannot be implemented in standard C++ and requires compiler support - cppreference

    From the quote, it immediately follows that both code samples do not conform to the C++ standard.

    There are null pointer dereference undefined behavior and reinterpret_cast conversion in the expression that is supposed to be constexpr. Both do not belong to core constant expressions:

    A core constant expression is any expression whose evaluation would not evaluate any one of the following:
     8. an expression whose evaluation leads to any form of core language undefined behavior ...
     18. reinterpret_cast

    The problem still persists in the second code sample because the function is not in fact constexpr, and the program is ill-formed.

    A constexpr function must satisfy the following requirements:

    • there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression ... (until C++23)

    For constexpr function templates and constexpr member functions of class templates, at least one specialization must satisfy the abovementioned requirements. Other specializations are still considered as constexpr, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required (until C++23). cppreference

    Consider example

    #include <type_traits>
    #include <iostream>
    
    struct A{};
    
    template<typename T, A T::*MPtr>
    class KKP {
    public:    // make the function public
        static constexpr size_t c() {
           return ((::size_t) & reinterpret_cast<char const volatile&>((((T*)0)->*MPtr)));
        }
    };
    
    struct PC {
        A t;
    };
    
    size_t f() {
        constexpr auto foo = KKP<PC, &PC::t>::c(); // fails to evaluate in the constexpr context
        return 0;
    }