A question about C++17 10.1.5 p1 s2: static data member and constexpr

141 views
Skip to first unread message

templ...@gmail.com

unread,
Oct 19, 2017, 10:24:16 AM10/19/17
to ISO C++ Standard - Discussion
I have a question about C++17 10.1.5 p1 s2:
" A ... static data member declared with the constexpr specifier is implicitly an inline ... variable"

Does this mean
"declared with the constexpr specifier in the class definition" (1) or
"declared with the constexpr specifier in the class definition and the definition in namespace scope" (2)?

For example:
------------
// A.h (e.g. from a library)
struct A
{
  static int const i;

};
------------
// A.cpp (from the same library)
#include "A.h"
int const A::i = 42; // definition by the author of A, an implementation detail that must not be compromised
------------
// main.cpp
#include "A.h"

// redefinition in C++17: anyone using A can compromise the implementation detail A::i
int constexpr A::i = 666; // in C++11/14: linker error, multiple definition (of course), but in C++17: OK, due to (2)

int main()
{
    return A::i; // in C++17: returns 666 instead of 42, that's not what the author of A wants
}
------------

This means with (2) the implementation of A can be compromised from outside. It also means that it breaks existing code (that is at most only 6 years old) due to a new language feature (inline variable).
With (1) it would be identical to C++11/14 (as I would expect, frankly).


Another example in which external linkage is transformed into internal linkage:
------------
// B.h
struct B
{
  static int const i; // external linkage since C++98
};
------------
// B.cpp
#include "B.h"
constexpr int const B::i = 42; // definition
of variable with external linkage, with constexpr
------------

// main.cpp
#include "B.h"
int main()
{
    return B::i; // in C++11/14: OK, returns 42, but in C++17: error, undefined reference to B::i, it has internal linkage due to (2)
}
------------

Again, with (2) existing code is broken. With (1) it would still work.
IMHO (2) is also contrary to the following because I don't see any reason why B::i and i should behave differently:
------------
// C.h
extern int const i; // essentially like a B::i in namespace scope
------------
// C.cpp
#include "C.h"
constexpr int const i = 42; // definition of variable with external linkage, just like B::i
------------
// main.cpp
#include "C.h"
int main()
{
    return i; // OK in C++17, no undefined reference to i, a different behavior compared to B::i with (2)
}

------------

I used Wandbox with the latest clang and gcc.
I asked a question on stackoverflow:
https://stackoverflow.com/questions/46726390/constexpr-defining-static-data-member-of-literal-type-that-is-declared-const
But it seems I'm the only person who thinks that (2) is not good.
Maybe the compilers are buggy. Or the standard is unclear. Or I simply don't get the idea.
Can anyone help?

Jens Maurer

unread,
Oct 19, 2017, 10:31:30 AM10/19/17
to std-dis...@isocpp.org

Your examples seem to violate the one-definition rule [basic.def.odr]
6.2p6, because the multiple definitions for A::i are not token-by-token
identical.

Jens



On 2017-10-19 16:24, templ...@gmail.com wrote:
> I have a question about C++17 10.1.5 p1 s2:
> " A ... static data member declared with the constexpr specifier is
> implicitly an inline ... variable"
>
> Does this mean
> "declared with the constexpr specifier *in the class definition*" (1) or
> "declared with the constexpr specifier *in the class definition and the
> definition in namespace scope*" (2)?
> constexpr int const B::i = 42; // definitionof variable with external
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "ISO C++ Standard - Discussion" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to std-discussio...@isocpp.org
> <mailto:std-discussio...@isocpp.org>.
> To post to this group, send email to std-dis...@isocpp.org
> <mailto:std-dis...@isocpp.org>.
> Visit this group at
> https://groups.google.com/a/isocpp.org/group/std-discussion/.

templ...@gmail.com

unread,
Oct 19, 2017, 11:06:04 AM10/19/17
to ISO C++ Standard - Discussion
Yes, but with intent, because I expected C++17 to be identical to C++11/14 and report that violation.
Do you mean that compilers do not need to diagnose this violation in C++17 anymore as they did in C++11/14?
And as a result they are free to do anything?
Or is it now undefined behavior?
I'm sorry I don't know exactly what you mean.

Jens Maurer

unread,
Oct 19, 2017, 1:16:41 PM10/19/17
to std-dis...@isocpp.org
On 2017-10-19 17:06, templ...@gmail.com wrote:
> Yes, but with intent, because I expected C++17 to be identical to
> C++11/14 and report that violation.
> Do you mean that compilers do not need to diagnose this violation in
> C++17 anymore as they did in C++11/14?

Compilers do not need to diagnose violations
of the one-definition rule in any case. And since
your program is not valid C++, there are no
requirements on your compiler to produce anything
useful at all.

It might be that some compilers/linkers happen to
diagnose the case of non-inline variables.

> And as a result they are free to do anything?
> Or is it now undefined behavior?

There is no difference between those two outcomes.

> I'm sorry I don't know exactly what you mean.

Your program is not valid C++ and the compiler
is not required to diagnose that fact in this
particular situation.

Jens
> > an email to std-discussio...@isocpp.org <javascript:>
> > <mailto:std-discussio...@isocpp.org <javascript:>>.
> > To post to this group, send email to std-dis...@isocpp.org
> <javascript:>
> > <mailto:std-dis...@isocpp.org <javascript:>>.
> <https://groups.google.com/a/isocpp.org/group/std-discussion/>.
>
>
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "ISO C++ Standard - Discussion" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to std-discussio...@isocpp.org
> <mailto:std-discussio...@isocpp.org>.

templ...@gmail.com

unread,
Oct 19, 2017, 2:50:42 PM10/19/17
to ISO C++ Standard - Discussion
Yes, I know it is invalid C++ in C++11/14 mode. That is the intent.
In C++11/14 mode both compilers define A::i twice but with different values which violates ODR, and the linkers diagnose that.
This is what I wanted (although I have to admit that I didn't know that there's actually "no diagnostic required").

But why is there no diagnostic in C++17 mode anymore? Your last sentence implies that this is invalid C++, too.
Why do the compilers/linkers behave differently?

And if you have time (and interest, of course): what about my actual question? Or the examples B and C?
>      > <mailto:std-discussion+unsub...@isocpp.org <javascript:>>.
>      > To post to this group, send email to std-dis...@isocpp.org
>     <javascript:>
>      > <mailto:std-dis...@isocpp.org <javascript:>>.
>      > Visit this group at
>      > https://groups.google.com/a/isocpp.org/group/std-discussion/
>     <https://groups.google.com/a/isocpp.org/group/std-discussion/>.
>
>
> --
>
> ---
> You received this message because you are subscribed to the Google
> Groups "ISO C++ Standard - Discussion" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to std-discussio...@isocpp.org

Nicol Bolas

unread,
Oct 19, 2017, 6:30:45 PM10/19/17
to ISO C++ Standard - Discussion, templ...@gmail.com


On Thursday, October 19, 2017 at 2:50:42 PM UTC-4, templ...@gmail.com wrote:
Yes, I know it is invalid C++ in C++11/14 mode. That is the intent.
In C++11/14 mode both compilers define A::i twice but with different values which violates ODR, and the linkers diagnose that.
This is what I wanted (although I have to admit that I didn't know that there's actually "no diagnostic required").

But why is there no diagnostic in C++17 mode anymore?

Because the standard doesn't require one. A compiler can catch some ODR violation errors and not others. Which ones it issues a diagnostic for, or any at all, can change from version to version.

Anything more specific than that should be something you ask the compiler vendor, not us.

templ...@gmail.com

unread,
Oct 20, 2017, 2:44:37 AM10/20/17
to ISO C++ Standard - Discussion, templ...@gmail.com
I guess you misunderstood my question.
Really I don't care at all why compilers behave the way they do or why they may behave differently from version to version.
That is really not the question here.
The question simply is: why does A::i with the value 666 compile in C++17 mode?
Is it:
(a) because it's ODR again (just like in C++11/14 mode), but the compilers simply choose to behave differently (for whatever reason that I absolutely don't care about)
(b) because the compilers consider A::i with the value 666 to be an inline variable due to the interpretation (2) of 10.1.5 p1 s2

Jens Maurers and your comments both seem to imply (a), that is, A::i with the value 666 is not an inline variable, that is, the interpretation (2) of 10.1.5 p1 s2 is false.
If that is really the case then example B must compile in C++17 mode as it does in C++11/14 mode. But it does not. This would imply a bug in both compilers.

I could also simply ask (forget about all three examples): is (1) true or is (2) true?
If (1) is true then both compilers are buggy and the wording in the standard may be imprecise. If (2) is true then I just don't get the idea.

Or I could also ask (see all three examples): are the compilers buggy (erroneously interpretation (2)) or am I buggy (erroneously interpretation (1))?

Richard Smith

unread,
Oct 20, 2017, 6:21:37 PM10/20/17
to std-dis...@isocpp.org
Example A violates [dcl.inline]p6: "If the definition of a function or variable appears in a translation unit before its first declaration as inline, the program is ill-formed. If a function or variable with external linkage is declared inline in one translation unit, it shall be declared inline in all translation units in which it appears; no diagnostic is required."

It also violates the ODR (because the initializers are different in different TUs), but it would be invalid even if they were the same due to the above rule.


I think Example B is interesting and worth discussing, because it is indeed a breaking change between C++14 and C++17. A conforming implementation can choose to keep existing C++11 or C++14 code working by emitting a non-discardable definition of B::i in B.cpp. Perhaps that should be required.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.

Karsten

unread,
Oct 21, 2017, 5:17:31 AM10/21/17
to ISO C++ Standard - Discussion
OK, now three experts have replied, but unfortunately I'm even more confused...
I clarify a bit because it seems I'm somehow still misunderstood.

I really wanted example A to be invalid. I really know that it's an ODR violation in C++11/14. And I expected it to be just the same in C++17 because my interpretation of 10.1.5 p1 s2 is (1), that is, "in the class definition only".
Now, clang and gcc do report the violation in C++11/14 mode as I expected. But they do not in C++17 mode. Instead they produce an executable.
So, obviously something has changed from C++11/14 mode to C++17 mode. The question before me is now: is it because A::i in main.cpp is now an inline variable? That is, is the interpretation (2) of 10.1.5 p1 s2 correct?

Your comment on example A implies that interpretation (2) is correct.
And the executable produced by clang and gcc is pure coincidence and freedom of choice because after all it is completely invalid code.

So, please let me know:
Is A::i in main.cpp implicitly an inline variable because it is declared constexpr?
That is, is interpretation (2) of 10.1.5 p2 s1 correct?

If this question must be answered "yes" then I'm really very surprised, for the following reasons:
First, example B shows a breaking change in the language from C++11/14 to C++17. Can this be the intent of the committee?
Second, the completely invalid example A shows a breaking change in the outcome (at least with clang and gcc): an executable instead of an error. Of course, one can argue that this is simply internal compiler stuff and bad luck for the programmer.
Third, I really don't see a substantial difference between B::i and i in example C, yet B::i in B.cpp is now an inline variable and i in C.cpp is not.
And finally I really did think that this example in C++11/14

struct S
{
 
static int constexpr i = 42; // must be defined exactly once if odr-used
};

just naturally evolved per (1) in C++17:
S::i is now language-wise an inline variable, so (contrary to C++11/14) no more definition required if odr-used (though still possible but deprecated)

Richard, you're in the committee, would you share some insight into what speaks against interpretation (1)? Or why (2) is better than (1)?
Thanks so far.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.

Karsten

unread,
Dec 10, 2017, 3:38:31 AM12/10/17
to ISO C++ Standard - Discussion
It seems you have changed the behavior in clang. I've compiled the latest trunk of LLVM/clang for Windows and B::i has external linkage now and is no longer considered an inline variable with internal linkage. So do you agree that (1) is the correct (intended) interpretation?
By the way, Microsoft is of the same opinion and will interpret it like (1) in the next release of MSVC (15.6) which will implement inline variables.
But GCC still interprets it like (2). I wonder if this confusion is a core language issue and worth a defect report.


On Saturday, October 21, 2017 at 12:21:37 AM UTC+2, Richard Smith wrote:
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
Reply all
Reply to author
Forward
0 new messages