40

I'm trying to compile this code, but g++ complains about ZERO having an incomplete type. Does this mean that in C++ a struct cannot contain a static constexpr instance of itself? If so, why?

struct Cursor
{
    size_t row,column;

    static constexpr Cursor ZERO {0,0};
    //error: constexpr const Cursor Cursor::ZERO has incomplete type
};

EDIT: I understand that Cursor cannot have a complete type when I declare ZERO. What I'd like to know is: is there any way I can have ZERO belonging to Cursor and still being constexpr?

11
  • could you provide error message? Commented Apr 3, 2015 at 12:35
  • 2
    I suspect this is because of constexpr & inline initialization. Commented Apr 3, 2015 at 12:36
  • Move the initialization outside of the class declaration. Commented Apr 3, 2015 at 12:37
  • @πάνταῥεῖ Can't do that with static constexpr member. Commented Apr 3, 2015 at 12:38
  • @πάνταῥεῖ: You can't, bub. Commented Apr 3, 2015 at 12:38

5 Answers 5

26

Unfortunately, you simply cannot do this!

Some static constexpr members may be initialised inline:

[C++11 9.4.2/3]: [..] 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. [..]

Cursor is a literal type, so this counts.

And the use of Cursor itself as a static data member within its own type is not a problem, as long as you initialise it at lexical namespace scope:

[C++11: 9.4.2/2]: The declaration of a static data member in its class definition is not a definition and may be of an incomplete type other than cv-qualified void. The definition for a static data member shall appear in a namespace scope enclosing the member’s class definition. In the definition at namespace scope, the name of the static data member shall be qualified by its class name using the :: operator. The initializer expression in the definition of a static data member is in the scope of its class (3.3.7).

But you can't do that with constexpr:

[C++11: 7.1.5/9]: A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. [..]

I think all of this wording could be improved but, in the meantime, I think you're going to have to make ZERO a non-member in the enclosing namespace.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for this complete answer. Given that Cursor is actually defined inside class VGA, I'll just make ZERO a static constexpr member of VGA. Sometimes I find certain restriction quite annoying...
16

is there any way I can have ZERO belonging to Cursor and still being constexpr?

Yes, if you count nested subclasses as "belonging to" the containing class:

struct Cursor
{
    size_t row,column;

    struct Constants;
};

struct Cursor::Constants
{
    static constexpr Cursor ZERO {0,0};
};

Comments

16

This answer to a similar question revealed that this is, in fact, possible to accomplish. You just have to put the constexpr keyword with the definition rather than the declaration:

#include <iostream>
#include <array>

struct Cursor
{
    static Cursor const ZERO;
    std::size_t row, column;
};

constexpr Cursor const Cursor::ZERO{ 0, 0 };

int main(int, char**) noexcept
{
    // using the values in a template argument ensure compile-time usage
    std::array<int, Cursor::ZERO.row> row_arr{};
    std::array<int, Cursor::ZERO.column> col_arr{};
    std::cout << "rows: " << row_arr.size() << "\ncols: " << col_arr.size();
    return 0;
}

Fully tested with GCC (ideone), clang (rextester), and MSVC++ 2017 (note that IntelliSense doesn't like it, but it compiles correctly!).

6 Comments

It does indeed compiles (with C++11/14) but Cursur::ZERO is not a constexpr in that case. I only managed to make it constexpr in C++17 if I inline the definition in the header file inline constexpr Cursor const Cursor::ZERO{ 0, 0 };. But do you have any idea to make it constexpr in the C++11 ? In other words, it should compile for constexpr Cursor zero = Cursor::Zero;.
Precision, the example above works only for one translation unit. For multiple translation units you need to inline the definition of the ZERO which if only available from C++17.
@AlexandreA. depending on your compiler, you can use a class template as an implementation detail. IIRC, MSVC didn't allow that method under C++11, but this works on GCC with -std=C++11 across multiple translation units (of course the link on Ideone isn't representative of that, but I tested it before posting).
This doesn't work on clang, even with C++17: constexpr variable cannot have non-literal type [...] is not complete until the closing '}'.
Not sure if this is a compiler bug, but inline constexpr of a member field in this manner in multiple translation units under MSVC15 still results in multiple-symbol link errors.
@Miral I haven't been able to find in the C++17 standard whether this construct is valid or not, though if it isn't then it should result in a compile-time error, not a link-time error. If the definition has inline or constexpr and the compiler accepts this yet produces a normal (not linkonce/comdat) data symbol for it, I'd definitely call that a compiler bug.
12

You can if you accept to have a function, not a variable

struct Cursor
{
    size_t row,column;

    static constexpr Cursor ZERO() { return Cursor{0,0}; }
};

Comments

3

A bit late, but here's a workaround if all you need is a constant:

struct Cursor
{
    struct CursorInit
    {
        int a, b;
        constexpr operator Cursor() const;
    };

    int row, column;
    static constexpr CursorInit ZERO {0,0};

    constexpr bool operator==(const Cursor& rhs) const
    {
        return row == rhs.row && column == rhs.column;
    }
};

inline constexpr Cursor::CursorInit::operator Cursor() const
{
    return Cursor{a, b};
}

static_assert(Cursor::ZERO == Cursor{0, 0});

1 Comment

I appreciate the cleverness. For some library interface I might actually employ this trick.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.