15

I have the following sample class Foo with nested class Bar and everything is constexpr:

class Foo
{
private:
    template <typename T>
    struct Bar
    {
        constexpr Bar(){}
        constexpr int DoTheThing() const
        {
            return 1;
        }
    };
        
public:
    constexpr static auto b = Bar<int>{};
    constexpr Foo() {}
    constexpr int DoTheThing() const
    {
        return b.DoTheThing();
    }
};

And I want to test that calling Foo::DoTheThing returns 1:

int main()
{
   constexpr Foo f;
   static_assert(f.DoTheThing() == 1, "DoTheThing() should return 1");
}

GCC and Clang both complain here, but MSVC does not

GCC says:

error: constexpr Foo::Bar<T>::Bar() [with T = int] used before its definition

constexpr static auto b = Bar<int>{};

And Clang:

error: constexpr variable b must be initialized by a constant expression

constexpr static auto b = Bar<int>{};

I cannot tell if the standard disallows this, but my guess is that somehow b is an incomplete type.

What makes things more interesting is that I can get GCC and Clang to behave if I remove the constexpr, or if I move the definition of Bar outside of Foo.

Which of these compilers is correct?

Note that this question was inspired by the following:

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • The fact that moving `Bar` outside of `Foo` works suggests to me that a factor here is the the fact that the definition of `Foo` is not complete until the class is completely declared. Until the end of `Foo`'s definition, its inner classes are not considered to be fully defined (which is why inline clsas methods can refer to class members that are declared after them). – Sam Varshavchik Sep 08 '16 at 01:12
  • 3
    `Bar` is already a [literal type](http://en.cppreference.com/w/cpp/concept/LiteralType), so there is no need for you to define a default constructor (in this example). Clang and GCC [compile fine](http://melpon.org/wandbox/permlink/ntRr69Ab6uMEmIGf) if you take out Bar's constructor. I know that doesn't answer your question, but I thought it was worth mentioning. – Barrett Adair Sep 08 '16 at 01:22
  • @SamVarshavchik I was initially inclined to agree with you, except that simply removing the `constexpr static` from `b` allows [everything to work just fine](http://coliru.stacked-crooked.com/a/89a893cc109d800a). – AndyG Sep 08 '16 at 13:22

1 Answers1

6

From n4140

§ 9.2.2 [class.mem] (Emphasis mine)

A class is considered a completely-defined object type (3.9) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, using-declarations introducing inheriting constructors (12.9), exception-specifications, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.

Clang and GCC are correct. The class is not considered complete when you are declaring your static constexpr member, so you cannot construct it. This is why moving the definition of Bar out or removing the static constexpr works (because it is considered complete when defining non-static members)


To clarify, especially considering this question: Static constexpr member of an inner class

The standardese I quoted above basically means that unless otherwise specified a class is regarded incomplete within itself *. A static, constexpr, or static constexpr initializer does not fall under the otherwise specified portion, and therefore we can not use anything declared within the class, which includes a nested class type.

*meaning you can't use it or members of it within the class declaration. The most well known exception to that is within a member function.

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • 3
    Um... But how come I can do this: `constexpr static auto b = sizeof(Bar);` The second part of your answer basically states that incompleteness of the outer type also implies incompleteness of the nested type (even if the latter "appears" to be complete). But `sizeof` also requires a *complete* type as its argument. How come it compiles with this nested type? – AnT stands with Russia Feb 13 '19 at 02:22
  • 1
    @AnT: I'm more convinced that the standard is underspecified here. If I default the nested class' `constexpr` constructor, then it will work. This kind of makes sense, because then the compiler can guarantee that the constructor doesn't attempt to access any aspect of the outer class. Otherwise, you could reference e.g., a `static constexpr` member of the outer class, which complicates things. I say the standard is "underspecified" because I think based on how it's written the code in the OP should be well-formed, but implementers saw that no real guarantees could be made if they allowed it. – AndyG Feb 15 '19 at 14:46
  • @AnT: Specifically I mean [this case](https://wandbox.org/permlink/4jsFcr0iGFRxqjSu) where the nested class refers to a `static constexpr` member of the parent class (legal), but the parent's `static constexpr` member is an instance of the nested class. In general, until the outermost class is complete, a compiler cannot allow construction (even `static constexpr`) of a nested class unless it can guarantee some kind of circular reference doesn't happen (which is easy to guarantee with a defaulted ctor) – AndyG Feb 15 '19 at 17:05
  • Besides the completeness issue, there are also issues about whether a constexpr member function can be used in a constant expression if its class is incomplete. See Core Language Issue [1255](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1255) and [1626](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1626) – xskxzr Aug 04 '21 at 05:36