Can't a class have static constexpr member instances of itself?
Asked Answered
C

4

52

This code is giving me incomplete type error. What is the problem? Isn't allowed for a class to have static member instances of itself? Is there a way to achieve the same result?

struct Size
{
    const unsigned int width;
    const unsigned int height;

    static constexpr Size big = { 480, 240 };

    static constexpr Size small = { 210, 170 };

private:

    Size( ) = default;
};
Cheremkhovo answered 3/3, 2016 at 18:32 Comment(4)
Are you asking specifically about constexpr static members ?Helban
@PiotrSkotnicki Yes. Removing the keyword does'n make it work anyway.Cheremkhovo
Once you remove the keyword, you can initialize it ouside the class I guess, when it's already a complete typeHelban
Does this answer your question? static constexpr member of same type as class being definedResin
T
47

Is there a way to achieve the same result?

By "the same result", do you specifically intend the constexpr-ness of Size::big and Size::small? In that case maybe this would be close enough:

struct Size
{
    const unsigned int width = 0;
    const unsigned int height = 0;

    static constexpr Size big() {
        return Size { 480, 240 };
    }

    static constexpr Size small() {
        return Size { 210, 170 };
    }

private:

    constexpr Size() = default;
    constexpr Size(int w, int h )
    : width(w),height(h){}
};

static_assert(Size::big().width == 480,"");
static_assert(Size::small().height == 170,"");
Titer answered 3/3, 2016 at 19:58 Comment(0)
D
64

A class is allowed to have a static member of the same type. However, a class is incomplete until the end of its definition, and an object cannot be defined with incomplete type. You can declare an object with incomplete type, and define it later where it is complete (outside the class).

struct Size
{
    const unsigned int width;
    const unsigned int height;

    static const Size big;
    static const Size small;

private:

    Size( ) = default;
};

const Size Size::big = { 480, 240 };
const Size Size::small = { 210, 170 };

see this here: http://coliru.stacked-crooked.com/a/f43395e5d08a3952

This doesn't work for constexpr members, however.

Damalas answered 3/3, 2016 at 18:38 Comment(3)
Do you think this does not work for constexpr members because forbidden by the standard or because of a compiler bug?Cheremkhovo
@Cheremkhovo It doesn't work because a static constexpr member is required to be initialized inline.Damalas
What about this approach?Sublapsarianism
T
47

Is there a way to achieve the same result?

By "the same result", do you specifically intend the constexpr-ness of Size::big and Size::small? In that case maybe this would be close enough:

struct Size
{
    const unsigned int width = 0;
    const unsigned int height = 0;

    static constexpr Size big() {
        return Size { 480, 240 };
    }

    static constexpr Size small() {
        return Size { 210, 170 };
    }

private:

    constexpr Size() = default;
    constexpr Size(int w, int h )
    : width(w),height(h){}
};

static_assert(Size::big().width == 480,"");
static_assert(Size::small().height == 170,"");
Titer answered 3/3, 2016 at 19:58 Comment(0)
S
6

As a workaround you can use a separate base class which definition is complete when defining the constants in the derived class.

struct size_impl
{
//data members and functions here
    unsigned int width;
    unsigned int height;
};


struct size:  public size_impl
{
//create the constants as instantiations of size_impl
    static constexpr size_impl big{480,240};
    static constexpr size_impl small{210,170};

//provide implicit conversion constructor and assignment operator
    constexpr size(const size_impl& s):size_impl(s){}
    using size_impl::operator=;

//put all other constructors here
};

//test:
constexpr size a = size::big;

You can put the base class in a separate namespace to hide its definition if you want to.

The code compiles with clang and gcc

Stingy answered 26/6, 2019 at 12:39 Comment(0)
P
0

Another possible workaround is to delay the need for size's definition with the use of templates.

template<typename...>
class size {
public:
    template<typename...>
    static constexpr auto big = size{480, 240};

    template<typename...>
    static constexpr auto small = size{210, 170};

    unsigned const width;
    unsigned const height;

private:
    constexpr size() = default;
    constexpr size(unsigned w, unsigned h)
        : width{w}, height{h} {}
};

static_assert(noexcept(size<>::big<>), "");

Live example

Pirri answered 15/3, 2023 at 2:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.