35

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?

lodo
  • 2,314
  • 19
  • 31

5 Answers5

25

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 4
    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... – lodo Apr 03 '15 at 12:49
15

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};
};
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
15

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!).

monkey0506
  • 2,489
  • 1
  • 21
  • 27
  • 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;`. – Alexandre A. Mar 11 '19 at 11:50
  • 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. – Alexandre A. Mar 11 '19 at 12:10
  • 1
    @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](https://ideone.com/YWWOTB) 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). – monkey0506 Mar 14 '19 at 05:57
  • 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 Jul 07 '20 at 02:40
  • @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. – Matthijs Apr 10 '21 at 05:38
10

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}; }
};
galinette
  • 8,896
  • 2
  • 36
  • 87
1

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});
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • I appreciate the cleverness. For some library interface I might actually employ this trick. – sehe May 14 '23 at 23:53