3

MSVC 2017 Community with -std=c++17 chokes on the following example:

#include <iostream>

struct TC
{
    static TC const values[];
    static TC const& A;
    static TC const& B;
    static TC const& C;

    int const _value;
};

inline constexpr TC const TC::values[]{ { 42 }, { 43 }, { 44 } };
inline constexpr TC const& TC::A{ values[0U] };
inline constexpr TC const& TC::B{ values[1U] };
inline constexpr TC const& TC::C{ values[2U] };

int main(int, char**) noexcept
{
    std::cout << std::boolalpha
        << "&A == &values[0]? " << (&TC::A == &TC::values[0U]) << "\n" 
        << "&B == &values[1]? " << (&TC::B == &TC::values[1U]) << "\n"
        << "&C == &values[2]? " << (&TC::C == &TC::values[2U]) << "\n";
    return 0;
}

The expected output is:

&A == &values[0]? true
&B == &values[1]? true
&C == &values[2]? true

Which is what both gcc and clang produce, but MSVC gives:

&A == &values[0]? true
&B == &values[1]? false
&C == &values[2]? false

MSVC does give the correct results if the _value member is removed and there is no user-defined constructor.

As all of this is within a single translation unit, my understanding is that this falls under Partially-ordered dynamic initialization:

2) Partially-ordered dynamic initialization, which applies to all inline variables that are not an implicitly or explicitly instantiated specialization. If a partially-ordered V is defined before ordered or partially-ordered W in every translation unit, the initialization of V is sequenced before the initialization of W (or happens-before, if the program starts a thread)

I cannot use a function to ensure initialization order as I require constexpr values and constexpr references to them.

So the question is, MSVC is in violation of the standard* here, correct?

*Of course cppreference.com isn't "the standard", but I'm assuming that the information there is correctly sourced.

M.M
  • 138,810
  • 21
  • 208
  • 365
monkey0506
  • 2,489
  • 1
  • 21
  • 27
  • Since when is it legal to declare an object as non-constexpr then define it as constexpr..? – ildjarn May 18 '18 at 03:56
  • Sounds like something to submit as feedback to MS (either under VS' help menu -> submit feedback) or perhaps https://developercommunity.visualstudio.com/spaces/62/index.html (it used to be connect.microsoft.com but that has apparently been discontinued). – SoronelHaetir May 18 '18 at 04:49
  • @ildjarn I can't see anything in the standard outlawing this usage – M.M May 18 '18 at 05:40
  • 1
    @ildjarn [Richard Smith cites the standard](https://stackoverflow.com/questions/21008861/initializing-a-static-constexpr-from-an-incomplete-type-because-of-a-template-ba/21589173#21589173) - "7.1.5/1 says 'The `constexpr` specifier shall be applied only to the definition of a variable...'" So `constexpr` (apparently) is not needed on the declaration. – monkey0506 May 18 '18 at 06:01
  • @monkey_05_06 In the "..." of that quote it goes on to list other places where `constexpr` can be applied, such as declarations. Also that quote is from C++11. – M.M May 18 '18 at 06:04
  • @M.M *can* be or **must** be? Because if it's the latter, then gcc, clang, and MSVC are all in violation of the standard in that respect. Also, this is currently the only way to define a `constexpr` object as a `static` member of its own defining class. – monkey0506 May 18 '18 at 06:07
  • @M.M I looked over N3797 (which I gather was the draft that was standardized as C++11), and unless the statements in 7.1.5/1 have changed in later standards, then `constexpr` is allowed on the "definition of a variable... **or** the declaration of a static data member of a literal type" (emphasis mine). It does require `constexpr` to match across all function declarations and variable template declarations, but it is explicitly **disallowed** on variable declarations *except* static members of a literal type, where it seems to be optional if there is a separate definition. – monkey0506 May 18 '18 at 06:34
  • @monkey_05_06 N3337 is the C++11 . N3797 was between C++11 and C++14 (which is N4140). I think you are misinterpreting that paragraph. (To discuss further post a separate question about that topic) – M.M May 19 '18 at 04:24
  • @M.M I did post [a separate question](https://stackoverflow.com/q/50440671/1136311), in which I cite the C++17 standard. Thanks for clarifying which paper I referenced here, but the text is largely still the same in C++17 anyway! – monkey0506 May 21 '18 at 00:51

1 Answers1

3

I isolate the same problem to this simpler example:

#include <iostream>

int values[3];
constexpr int& v{ values[1] };

int main()
{
    std::cout << &v << ", " << &values[1] << "\n";
    return 0;
}

which, using latest MSVC 2017 Community, gives output of:

0119D035, 0119D038

The problem does not happen if constexpr is removed.

So I think this is a compiler bug with constexpr reference initialization. The reference was initialized to &values[0] + 1 byte, not &values[1] as it should be.

NB. If anyone is not familiar with constexpr reference definitions, see here or here. constexpr enforces that the initializer is an object with static storage duration.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks for that confirmation! I'll give it a couple of days to see if anyone else has anything to add, but otherwise I'll mark this as the accepted answer and cite it in a defect report. Cheers! – monkey0506 May 18 '18 at 06:03
  • Unsurprisingly, MSVC still chokes initializing from an expression like `&values[0] + 1` or `std::addressof(values[0]) + 1`, but it actually works correctly if we use a bit of type punning on the pointer. `reinterpret_cast` fails (as it should), but both MSVC and gcc still allow `*static_cast(static_cast(static_cast(static_cast(&values[0])) + (sizeof(int) * 2)))`, and with that, MSVC actually binds the reference to the right object (as does gcc). clang doesn't allow such nonsense. – monkey0506 May 18 '18 at 09:02
  • I expanded on your test case [here](https://ideone.com/yTeLdT) (I couldn't find an online compiler for MSVC C++17). GCC gives exactly the expected output, but MSVC 2017 Community shows that every reference is off by exactly `3*n` bytes for the reference to item `IntArray[n]`. – monkey0506 May 22 '18 at 23:41
  • After a lot of trial and error trying to figure out the code formatting of Microsoft's Developer Community forums (pro-tip: there is none), I opted to just [post a screenshot instead](https://developercommunity.visualstudio.com/content/problem/258780/constexpr-reference-to-array-member-has-wrong-addr.html). Their forums also don't (*apparently*) allow hyperlinks, but I referenced the question number for this SO question. – monkey0506 May 23 '18 at 00:59