19
votes

Why is this constexpr static member function, identified by the //! Nah comment, not seen as constexpr when called?

struct Item_id
{
    enum Enum
    {
        size, position, attributes, window_rect, max_window_size, _
    };

    static constexpr int n_items_ = _;                          // OK
    constexpr auto member_n_items() const -> int { return _; }  // OK
    static constexpr auto static_n_items() -> int { return _; } // OK
    static constexpr int so_far = n_items_;                     // OK
    #ifndef OUT_OF_CLASS
        static constexpr int bah = static_n_items();            //! Nah.
    #endif
};

constexpr auto n_ids() -> int { return Item_id().member_n_items(); }    // OK

auto main() -> int
{
    #ifdef OUT_OF_CLASS
        static constexpr int bah = Item_id::static_n_items();   // OK
    #endif
}

MinGW g++ 5.1 reports

constexpr.cpp:12:46: error: 'static constexpr int Item_id::static_n_items()' called in a constant expression
     static constexpr int bah = static_n_items();                //! Nah.

Visual C++ 2015 reports

constexpr.cpp(12): error C2131: expression did not evaluate to a constant
constexpr.cpp(12): note: failure was caused by call of undefined function or one not declared 'constexpr'
constexpr.cpp(12): note: see usage of 'Item_id::static_n_items'

My text editor insists that the name in the call is the same as the name in the function definition.

It appears to have something to do with incomplete class, because with OUT_OF_CLASS defined it compiles nicely.

But then why does the n_items_ data work, and, why such a rule (doesn't make sense to me)?

2
gcc6.1 and clang3.8 don't like it either.Baum mit Augen♦
Note, moving the declaration and initialization of bah out of the struct and doing static constexpr int bah = Item_id::static_n_items(); makes this compile on G++ 5.3Hatted Rooster
@GillBates: Thanks, I found the same (see revised question). It's weird.Cheers and hth. - Alf

2 Answers

14
votes

From memory, member function bodies are evaluated only once the class has been completely defined.

static constexpr int bah = static_n_items(); 

forms part of the class definition, but it's referring to a (static) member function, which cannot yet be defined.

Solution:

defer constant expressions to a base class and derive from it.

e.g.:

struct Item_id_base
{
    enum Enum
    {
        size, position, attributes, window_rect, max_window_size, _
    };

    static constexpr int n_items_ = _;                          // OK
    constexpr auto member_n_items() const -> int { return _; }  // OK
    static constexpr auto static_n_items() -> int { return _; } // OK
    static constexpr int so_far = n_items_;                     // OK
};

struct Item_id : Item_id_base
{
    #ifndef OUT_OF_CLASS
        static constexpr int bah = static_n_items();            // now OK
    #endif
};

constexpr auto n_ids() -> int { return Item_id().member_n_items(); }    // OK

auto main() -> int
{
    #ifdef OUT_OF_CLASS
        static constexpr int bah = Item_id::static_n_items();   // OK
    #endif
}

Why do you think the standard disallows it?

Because this is illegal:

struct Item_id
{   
    // ... etc.

    #ifndef OUT_OF_CLASS
        static constexpr int bah;// = static_n_items();            //! Nah.
    #endif
};

constexpr int Item_id::bah = static_n_items();

And a constexpr must have a constexpr definition. The only place we can define it is during its declaration...

... so by deduction it cannot refer to any function who's body is not yet defined.

I am at a loss to know where to look in the standard for all that. Probably 5 different, seemingly unrelated clauses :)

5
votes

[class.mem]/2

Within the class member-specification, the class is regarded as complete within function bodies, default arguments, exception-specifications, and default member initializers (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.

In the initializer of a static data member of a class, the class is incomplete. The initializer can only see the declarations of members which precede it, and any member functions it can see are considered declared but not defined. A call to a function that is declared but not defined can not be a constant expression.