Two LLVM & GCC inconsistencies. Which is right?

Started by
9 comments, last by xaxazak 5 years, 8 months ago

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?

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.

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.

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)).

 

 

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.

 

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.

 

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.
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.

 

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.

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.

This topic is closed to new replies.

Advertisement