Bug 58969 - bogus error: the value of 'kName' is not usable in a constant expression
Summary: bogus error: the value of 'kName' is not usable in a constant expression
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: unknown
: P3 normal
Target Milestone: 7.2
Assignee: Not yet assigned to anyone
URL:
Keywords: rejects-valid
Depends on:
Blocks:
 
Reported: 2013-11-01 21:30 UTC by Paul Pluzhnikov
Modified: 2018-03-22 08:57 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2014-12-10 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Paul Pluzhnikov 2013-11-01 21:30:27 UTC
Google ref: b/11479502

/// --- cut ---
template <const char name[]>
class BaseObject {
 virtual const char* GetName() const {
   return name;
 }
};

const char kName[] = "name";
class Object : public BaseObject<kName> {
};

int main() {
 return 0;
}
/// --- cut ---

Using g++ (GCC) 4.9.0 20131028 (experimental)

g++ -c t.cc
t.cc:9:34: error: 'kName' cannot appear in a constant-expression
 class Object : public BaseObject<kName> {
                                  ^
t.cc:9:39: error: template argument 1 is invalid
 class Object : public BaseObject<kName> {
                                       ^

g++ -c t.cc -std=c++11
t.cc:9:34: error: the value of 'kName' is not usable in a constant expression
 class Object : public BaseObject<kName> {
                                  ^
t.cc:8:12: note: 'kName' was not declared 'constexpr'
 const char kName[] = "name";
            ^

Compiles with Clang.

Analysis by Richard Smith:

This is a GCC bug. It appears to be some sort of confusion in the way GCC handles internal linkage non-type template parameters of pointer type. You can also get GCC to accept the code by marking kName as 'extern'.


Obligatory standards references (relative to N3797):

[temp.param](14.1)/8: "A non-type template-parameter of type "array of T" [...] is adjusted to be of type "pointer to T".

[temp.arg.nontype](14.3.2)/1: "A template-argument for a non-type, non-template template-parameter shall be [...] a constant expression that designates the address of a complete object with static storage duration and external or internal linkage [...] expressed [...] as & id-expression, where the id-expression is the name of an object [...], except that the & may be omitted if the name refers to a[n] [...] array"

Note that "kName" is a constant expression, because it is a glvalue core constant expression whose value refers to an object with static storage duration (see [expr.const](5.19)/4).

[temp.arg.nontype](14.3.2)/5: "The following conversions are performed on each expression used as a non-type template-argument. [...] For a non-type template-parameter of type pointer to object, [...] the array-to-pointer conversion [is] applied."


So BaseObject has a template parameter of type 'const char*', and that parameter can bind to the template argument kName (after array-to-pointer decay). There's no requirement that kName be declared 'constexpr'; its value is not used here, only its address is used.
Comment 1 Kai Tietz 2014-12-10 22:08:51 UTC
Hmm, issue seems to be in too restrictive decl_maybe_constant_var_p function.
We could allow here additional ARRAY_TYPEs with constant, non-vla, and trivial destructors.  Maybe even non-trivial destructors could be ok.  Not sure

Suggested patch, which allows provided testcase to run is:

Index: decl2.c
===================================================================
--- decl2.c     (Revision 218570)
+++ decl2.c     (Arbeitskopie)
@@ -4157,8 +4157,12 @@ decl_maybe_constant_var_p (tree decl)
     return false;
   if (DECL_DECLARED_CONSTEXPR_P (decl))
     return true;
-  return (CP_TYPE_CONST_NON_VOLATILE_P (type)
-         && INTEGRAL_OR_ENUMERATION_TYPE_P (type));
+  if (!CP_TYPE_CONST_NON_VOLATILE_P (type))
+    return false;
+  return ((TREE_CODE (type) == ARRAY_TYPE
+          && !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (type))
+          && !array_of_runtime_bound_p (type))
+         || INTEGRAL_OR_ENUMERATION_TYPE_P (type));
 }

 /* Complain that DECL uses a type with no linkage.  In C++98 mode this is
Comment 2 Richard Smith 2014-12-10 22:25:58 UTC
(In reply to Kai Tietz from comment #1)
> Hmm, issue seems to be in too restrictive decl_maybe_constant_var_p function.

I don't know how the GCC code is structured, but I don't think that's right; that function appears to be checking whether the value of the variable can be used in a constant expression. The relevant condition here is whether the address of the variable can be used.
Comment 3 Kai Tietz 2014-12-11 10:36:46 UTC
(In reply to Richard Smith from comment #2)
> (In reply to Kai Tietz from comment #1)
> > Hmm, issue seems to be in too restrictive decl_maybe_constant_var_p function.
> 
> I don't know how the GCC code is structured, but I don't think that's right;
> that function appears to be checking whether the value of the variable can
> be used in a constant expression. The relevant condition here is whether the
> address of the variable can be used.

hmm, this function has nothing to do with its value.  AFAIU the comment (and its use) it just checks that a VAR-decl might be a constant.
In general it checks for const and volatile attributes, and assumes for integral/enumeral typed variables that variable is constant.
So a 'const char *' isn't constant - as just the destination the variable is pointing to is constant, but not the variable itself.  For a constant array with trivial destructor, and non-vla size this is different.  The array's name is indeed a constant address, and its content is constant too.  Of course the a variable pointing to into an array isn't constant, but the array itself is.

Anyway I might be wrong here
Comment 4 Jonathan Wakely 2018-03-22 08:57:49 UTC
This was fixed by r249079 on trunk and r249325 for 7.2

    Fix array decay handling in constant expressions.
    
    * parser.c (cp_parser_constant_expression): Check
    potential_rvalue_constant_expression after decay_conversion.
    * pt.c (convert_nontype_argument): Don't require linkage in C++17.