Jump to content
  • Advertisement
Sign in to follow this  
xaxazak

C++ Two LLVM & GCC inconsistencies. Which is right?

Recommended Posts

All compiled using -std=c++2a.

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

First - defining a constexpr member function of a template after use.

template <typename tTYPE>
struct SFoo
    {
        constexpr void foo();
    };

int main()
    {
        SFoo<int> foo;
        foo.foo();
    }

template <typename tTYPE>
constexpr void SFoo<tTYPE>::foo()
    {}

GCC compiles and links this fine. Clang doesn't instantiate foo(), and thus fails to link.

Quote

4:18: warning: inline function 'SFoo<int>::foo' is not defined [-Wundefined-inline]

NOTE: constexpr implies inline.

I think LLVM is wrong here - the definition just needs to be provided somewhere. Am I right?

Quote

 

The exact quote from n3290 § 7.1.2.4 is:

An inline function shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case (3.2). [ Note: A call to the inline function may be encountered before its definition appears in the translation unit. —end note ]

 

(From this post).

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

Second, default initialization of unused constexpr template value.

template <typename T>
constexpr int n;

int main()
    {}

Again, GCC accepts it, but clang++ complains:

Quote

2:15: error: default initialization of an object of const type 'const int'

If you want to only use specializations of n, requiring a value here is unnecessary (and could prevent detecting bugs at compile-time).

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

Are both of these LLVM bugs?

Share this post


Link to post
Share on other sites
Advertisement

If you want to really know, check the official C++ language definition for a rule that says what must happen in these specific cases. If a compiler behaves differently than the spec allows, it is a bug.

It can be perfectly fine to have two different compilers says different things about the same specification. By design, the C++ language specification gives a lot of freedom for the implementation to make their own decisions.

Share this post


Link to post
Share on other sites

You can't expect full consistency from an experimental mode for an upcoming standard thats still in flow. Best thing you could do is read commitee notes and compiler docs.

Share this post


Link to post
Share on other sites
Posted (edited)
1 hour ago, wintertime said:

You can't expect full consistency from an experimental mode for an upcoming standard thats still in flow. Best thing you could do is read commitee notes and compiler docs.

These aren't c++2a features - sorry I guess the 1st line was misleading (I just use 2a for everything). Both claim to fully support c++17, and using -std=c++17 instead makes no difference here (nor does -pedantic or -Wall).

 

4 hours ago, Alberth said:

If you want to really know, check the official C++ language definition for a rule that says what must happen in these specific cases. If a compiler behaves differently than the spec allows, it is a bug.

I do sometimes (many of my posts have spec references, including issue #1 here), but I'm not always that great at remembering where the pertinent details are (it's a huge document), it can take a very long time for me to hunt down relevant details. The topic issues are part of many separate sections. That's why I'm after a 2nd opinion before I post bugs (after checking for duplicates).

 

4 hours ago, Alberth said:

It can be perfectly fine to have two different compilers says different things about the same specification. By design, the C++ language specification gives a lot of freedom for the implementation to make their own decisions.

I'm not really sure if that's accurate. In almost all cases, code is either legal or illegal C++ (although there are a handful of extra features like runtime-size arrays - usually they can be disabled (eg with -pedantic). I'd say the specification gives no freedom regarding whether code should compile or not (except for explicitly undefined behavior, extensions, and builtins and other internal details (which are hidden inside ::std)).

 

 

Edited by xaxazak
added some minor clarification

Share this post


Link to post
Share on other sites

The C++ standard is a very complex document.  As such, it sometimes happens that it contains ambiguities that can be interpreted in multiple ways, and it sometimes contains text that does not reflect the true intention behind the standard.

In your first example, I would note that the general intention behind the C++ standard is that each namespace-scope declaration can be compiled without looking at the declarations below it.  This is why C++ has forward declarations.  This is possible for forward-declared inline functions (since the compiler can choose not to inline them), but not for forward-declared constexpr functions used in a constexpr context.  I would therefore argue that regardless of the actual text of the C++ standard, clang is at least consistent with its spirit.

Regarding your second example, I suspect (but have not actually checked) that clang is correct.  If const int n; at namespace scope is illegal, then constexpr int n; should also be illegal, with or without the template.  Note that just plain int n; at namespace scope is initialized to 0.  Therefore if constexpr int n; were legal, I would expect it to declare n to have a value of 0, which would not be useful for detecting bugs at compile-time.

Share this post


Link to post
Share on other sites
Posted (edited)

 

52 minutes ago, a light breeze said:

The C++ standard is a very complex document.  As such, it sometimes happens that it contains ambiguities that can be interpreted in multiple ways, and it sometimes contains text that does not reflect the true intention behind the standard.

True, there is occasional ambiguity (it's not supposed to be there, but it's not perfect). But do you have any reason to think ambiguity exists in these examples? They are pretty simple, fairly significant, and in no way obscure. If there is ambiguity here it's significant.

So if either of these are in fact real ambiguity, then I think it needs a defect report sent to the C++ standard committee (if one doesn't already exist).

 

52 minutes ago, a light breeze said:

In your first example, I would note that the general intention behind the C++ standard is that each namespace-scope declaration can be compiled without looking at the declarations below it.  This is why C++ has forward declarations.  This is possible for forward-declared inline functions (since the compiler can choose not to inline them), but not for forward-declared constexpr functions used in a constexpr context.  I would therefore argue that regardless of the actual text of the C++ standard, clang is at least consistent with its spirit.

I acknowledge that that was probably the intent - at least initially. But C++ isn't meant to be about intent and spirit, it's meant to be about following explicit rules. It's important for compilers to attempt to follow the text to the letter, because if you let them diverge, you then get libraries tested on different compilers that can't be used together.

Regarding example #1, I think it's unlikely that a compiler is going to forget about optimizing a template function call simply because its definition comes later (which is common and not illegal) so I doubt allowing this would incur much compiler dev time.

One big use-case here is avoiding cyclic dependencies. Having constexpr functions call eachother recursively can be useful.

 

52 minutes ago, a light breeze said:

Regarding your second example, I suspect (but have not actually checked) that clang is correct.  If const int n; at namespace scope is illegal, then constexpr int n; should also be illegal, with or without the template.  Note that just plain int n; at namespace scope is initialized to 0.  Therefore if constexpr int n; were legal, I would expect it to declare n to have a value of 0, which would not be useful for detecting bugs at compile-time.

Using const instead of constexpr doesn't change the result here. G++ still accepts. Clang still rejects.

"constexpr int n;" shouldn't be legal. The issue is whether "template <T> constexpr int n;" should be.

The "detect bug at compile time" was about attempting to use a fully-specialized version of the variable if that version hasn't been given a value. So "int x = n<int>;" will error unless n has been specialized for int.

 

Edited by xaxazak
added "committee" & "regarding example #1"

Share this post


Link to post
Share on other sites
49 minutes ago, xaxazak said:

"constexpr int n;" shouldn't be legal. The issue is whether "template <T> constexpr int n;" should be.

Are talking about what the rules actually are, or are we talking about what the rules should be?

Allowing template <T> constexpr int n; without allowing constexpr int n; would require a very specific rule for just this case.  There is no other rule that could possibly be extended to allow one without allowing the other.  Now, this rule either exists or it does not - I don't believe it does, but like I said, I haven't checked - but whether it should exist is another question entirely.  I do not believe this rule should exist, for two reasons:

  • It adds additional complexity to the language for minimal gain.
  • Its semantics are not clear.  Like I pointed out previously, int n; is equivalent to int n = 0; at namespace scope.  Therefore the natural semantics of template<class T> constexpr n;, if it were allowed, would be equivalent to template<class T> constexpr n = 0;.  Note that constexpr X n;, where X is a user-defined type with a constexpr default constructor, is legal and defined n to be a default-constructed X.

Share this post


Link to post
Share on other sites
Posted (edited)
58 minutes ago, a light breeze said:

Ok someone please tell me how to remove a quote box while making a comment :P [totally not SIC]

-----

58 minutes ago, a light breeze said:

Are talking about what the rules actually are, or are we talking about what the rules should be?

Are, sorry, so replace "should(n't) be" with "AFAIK is(n't)".

 

58 minutes ago, a light breeze said:

Are talking about what the rules actually are, or are we talking about what the rules should be?

Allowing template <T> constexpr int n; without allowing constexpr int n; would require a very specific rule for just this case.

To me, it seems like constexpr int n; fails on (compile-time) instantiation, which, for a non-template, is the same as definition.

But template <T> constexpr int n; isn't an instantiation. Any use of n<int> shouldn't give zero, it should fail to compile. But n<SZug<int>> could compile if n<SZug<T>> were specialized. At least, that's how template structs work.

For template structs, you can write lots of illegal stuff in them and they don't fail until you try to (compile-time) instantiate them. (However, I notice that static members of uninstantiated template structs require valid definitions if constexpr, but not if const. While that does seem sensible, it also seems a bit inconsistent - maybe the spec explicitly requires this).

But if I look at it the way you do I also see your point. And while I think it could be useful (I was using it), it's very easy to get the same effect using static_assert.

But (ir)regardless, either G++ or LLVM is getting it wrong, or the spec is ambiguous. So something needs a fix.

 

Edited by xaxazak
felt like it

Share this post


Link to post
Share on other sites

Now that I think about it, there is a workaround you can use to get the behavior you want with clang:

template<class T> struct int_ {
  typedef int type;
};

template<class T> constexpr typename int_<T>::type n;

Here the use of the int_ template forces the compiler to delay determining the type of n until instantiation time, even though that type is always int.  Instantiating n always results in an error unless n is specialized for that same type, but the declaration itself is legal.

Share this post


Link to post
Share on other sites

That works, too. Currently I'm assigning n to a static template member of a suicide struct which has static_assert(fail_bool<T>) [that's not the actual name]. It's slightly more verbose, but it lets me put a message inside static_assert.

Gonna browse the template variable stuff in the standard later tonight, see if I can figure out the legalese of it myself.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!