constexpr initializing static member using static function
Asked Answered
H

4

39

Requirements

I want a constexpr value (i.e. a compile-time constant) computed from a constexpr function. And I want both of these scoped to the namespace of a class, i.e. a static method and a static member of the class.

First attempt

I first wrote this the (to me) obvious way:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

g++-4.5.3 -std=gnu++0x says to that:

error: ‘static int C1::foo(int)’ cannot appear in a constant-expression
error: a function call cannot appear in a constant-expression

g++-4.6.3 -std=gnu++0x complains:

error: field initializer is not constant

Second attempt

OK, I thought, perhaps I have to move things out of the class body. So I tried the following:

class C2 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));

g++-4.5.3 will compile that without complaints. Unfortunately, my other code uses some range-based for loops, so I have to have at least 4.6. Now that I look closer at the support list, it appears that constexpr would require 4.6 as well. And with g++-4.6.3 I get

3:24: error: constexpr static data member ‘bar’ must have an initializer
5:19: error: redeclaration ‘C2::bar’ differs in ‘constexpr’
3:24: error: from previous declaration ‘C2::bar’
5:19: error: ‘C2::bar’ declared ‘constexpr’ outside its class
5:19: error: declaration of ‘const int C2::bar’ outside of class is not definition [-fpermissive]

This sounds really strange to me. How do things “differ in constexpr” here? I don't feel like adding -fpermissive as I prefer my other code to be rigurously checked. Moving the foo implementation outside the class body had no visible effect.

Expected answers

Can someone explain what is going on here? How can I achieve what I'm attempting to do? I'm mainly interested in answers of the following kinds:

  • A way to make this work in gcc-4.6
  • An observation that later gcc versions can deal with one of the versions correctly
  • A pointer to the spec according to which at least one of my constructs should work, so that I can bug the gcc developers about actually getting it to work
  • Information that what I want is impossible according to the specs, preferrably with some insigt as to the rationale behind this restriction

Other useful answers are welcome as well, but perhaps won't be accepted as easily.

Hoopen answered 17/7, 2012 at 12:26 Comment(1)
moving foo outside the class definition seems to resolve the issue in gcc 6.1: see here: linkSwap
F
27

The Standard requires (section 9.4.2):

A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression.

In your "second attempt" and the code in Ilya's answer, the declaration doesn't have a brace-or-equal-initializer.

Your first code is correct. It's unfortunate that gcc 4.6 isn't accepting it, and I don't know anywhere to conveniently try 4.7.x (e.g. ideone.com is still stuck on gcc 4.5).

This isn't possible, because unfortunately the Standard precludes initializing a static constexpr data member in any context where the class is complete. The special rule for brace-or-equal-initializers in 9.2p2 only applies to non-static data members, but this one is static.

The most likely reason for this is that constexpr variables have to be available as compile-time constant expressions from inside the bodies of member functions, so the variable initializers are completely defined before the function bodies -- which means the function is still incomplete (undefined) in the context of the initializer, and then this rule kicks in, making the expression not be a constant expression:

an invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;

Consider:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};
Fuchsin answered 17/7, 2012 at 13:6 Comment(9)
liveworkspace.org/code/b9764c378d246308970e50eb6378c3aa But if replace constexpr with const it works well even using gcc.Overeat
@Ilya: Thanks. Seems g++ 4.7.1 still is buggy wrt constexpr static member functions.Fuchsin
So I guess I'll eventually file a gcc bug about this, using your qutoe as an indication of what should work. Thanks a lot!Hoopen
@MvG: I added to my answer the stuff that should be relevant to a bug report.Fuchsin
Filed as bug 54002. Thank you for your part in this investigation, and be welcome to cc to that bug report.Hoopen
@MvG: They marked it correctly as a dupe of gcc.gnu.org/bugzilla/show_bug.cgi?id=52366 ... which is incorrectly closed. I would encourage you to reopen 52366 and point out that it deals with a brace-or-equal-initializer, which is NOT the same as an array dimension.Fuchsin
@MvG: I read that rule in the Standard a little more closely, and it actually doesn't help your situation. Looks like this is forbidden.Fuchsin
OK, I believe I now understand the problem here: the class definition is not only complete inside the equals initializer, but also complete inside the function body. And a complete class defintion would mean actual values for all constexpr members. So the function definition has to occur after constant evaluation, and foo is in fact undefined. At least that's the interpretation which bug 52315 suggests. Do you want to include that in your answer?Hoopen
A static data member of literal type can be declared in the class definition with the constexpr specifier. Can does not mean must. I would interpret this as a deficiency.Trinia
U
5

1) Ilya's example should be invalid code based on the fact that the static constexpr data member bar is initialized out-of-line violating the following statement in the standard:

9.4.2 [class.static.data] p3: ... A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression.

2) The code in MvG's question:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

is valid as far as I see and intuitively one would expect it to work because the static member foo(int) is defined by the time processing of bar starts (assuming top-down processing). Some facts:

  • I do agree though that class C1 is not complete at the point of invocation of foo (based on 9.2p2) but completeness or incompleteness of the class C1 says nothing about whether foo is defined as far as the standard is concerned.
  • I did search the standard for the definedness of member functions but didn't find anything.
  • So the statement mentioned by Ben doesn't apply here if my logic is valid:

    an invocation of an undefined constexpr function or an undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor;

3) The last example given by Ben, simplified:
class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};

looks invalid but for different reasons and not simply because foo is called in the initializer of bar. The logic goes as follows:

  • foo() is called in the initializer of the static constexpr member bar, so it has to be a constant expression (by 9.4.2 p3).
  • since it's an invocation of a constexpr function, the Function invocation substitution (7.1.5 p5) kicks in.
  • Their are no parameters to the function, so what's left is "implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization." (7.1.5 p5)
  • the return expression is just bar, which is a lvalue and the lvalue-to-rvalue conversion is needed.
  • but by bullet 9 in (5.19 p2) which bar does not satisfy because it is not yet initialized:

    • an lvalue-to-rvalue conversion (4.1) unless it is applied to:
      • a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression.
  • hence the lvalue-to-rvalue conversion of bar does not yield a constant expression failing the requirement in (9.4.2 p3).

  • so by bullet 4 in (5.19 p2), the call to foo() is not a constant expression:

    an invocation of a constexpr function with arguments that, when substituted by function invocation substitution (7.1.5), do not produce a constant expression

Upcountry answered 3/8, 2012 at 17:6 Comment(0)
O
3
#include <iostream>

class C1 
{
public:
    constexpr static int foo(constexpr int x)
    { 
        return x + 1;
    }

    static constexpr int bar;
};

constexpr int C1::bar = C1::foo(sizeof(int));

int main()
{
    std::cout << C1::bar << std::endl;
    return 0;
}

Such initialization works well but only on clang

Overeat answered 17/7, 2012 at 12:36 Comment(2)
As I'm using gcc-specific stuff in several places, switching to clang doesn't feel right for this. While I'm still waiting for an answer for gcc, the fact that clang supports this might indicate a bug in gcc's implementation. Any authoritative answer to that from the specs would be welcome.Hoopen
gcc is correct (for this code), clang is wrong (or this is a non-portable "extension").Fuchsin
A
3

Probably, the problem here is related to the order of declaration/definitions in a class. As you all know, you can use any member even before it is declared/defined in a class.

When you define de constexpr value in the class, the compiler does not have the constexpr function available to be used because it is inside the class.

Perhaps, Philip answer, related to this idea, is a good point to understand the question.

Note this code which compiles without problems:

constexpr int fooext(int x) { return x + 1; }
struct C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = fooext(5);
};

constexpr static int barext = C1::foo(5);
Actinal answered 12/7, 2016 at 8:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.