tomascz
tomascz

Reputation: 245

Static constexpr data member initialized off-class, and a possible workaround?

From elsewhere I know that it's not possible in C++17 to declare a static constexpr data member without its immediate initialization (although yet elsewhere they use such example).

// --- in header ---
struct Data{
    LPCTSTR str;
    int i;
};

class C{
public:
    static constexpr Data MyData; // VS complains: this requires an in-class initializer
};

// --- in implementation file ---
constexpr Data C::MyData={
    _T("Hi"), 12345
};

My code must, for the time being, compile also in older Visual Studios where the lack of constexpr concept can easily be #defined as

#if _MSC_VER<=1600 // pre-C++11 compiler?
    #define constexpr const
#endif

and suddenly the code base would be compatible with virtually anything from VS6 to VS2019. Correct me if the off-class initialization is allowed by means of some hidden command-line switch in Visual Studio.

So I came up with a workaround about whose validity I'm not sure. Unlike data members, static constexpr methods don't have to have an in-class body. So I do:

// --- in header ---
struct Data{
    LPCTSTR str;
    int i;
};

class C{
public:
    static constexpr Data GetMyData();
};

// --- in implementation file ---
constexpr Data C::GetMyData(){
    constexpr Data D={
        _T("Hi"), 12345
    };
    return D;
}

I can then benefit from constexpr, yet still compile (with the help of the above #define) under older Visual Studios:

constexpr LPCTSTR Greeting=C::GetMyData().str;

and also surprisingly take the address of the returned object at run-time:

const Data *p=&C::GetMyData();
const LPCTSTR greeting=p->str;

which is great and desired in my app! But makes me worry about the address of what I have received? Is it the address of the object compiled into the resulting executable (and at run-time residing in the protected memory) or an address of a global variable created really at run-time? I of course want the earlier to occur when compiling under newer Visual Studios, whereas I don't care about the situation under pre-C++11 versions.

Thanks in advance.

Upvotes: 1

Views: 1100

Answers (1)

user4442671
user4442671

Reputation:

The key point is that constexpr does not mean "exists only at compile time", but instead "can also be used at compile time. So as long as you don't access the constexpr variable in a way that requires the value to be accessible at compile-time, it can be treated as if it was a const.

In fact, with the code you posted, the only place where you ever see the benefits of constexpr is inside of the implementation file. Anywhere else, it has not choice but to be "degrated" to a regular const since resolving its value during compilation is impossible.

I think most of your questions can be cleared up by deconstructing the code a bit into an equivalent example:

// MyClass.h
class MyClass {
  // static constexpr Data MyData;
  static const Data MyData;
};
// MyClass.cpp
namespace {
  constexpr Data MyDataValue = ...;
}

const Data MyData::MyData = MyDataValue;

// If you use MyData inside of the implementation file, it gets the conxtepr value.
void MyClass:foo() {
  // std::array<float, MyClass::Data.some_member> some_array; 
  std::array<float, MyDataValue.some_member> some_array; // no problem
}
// Some_other_file.cpp

int foo() {
  // That's fine, MyClass::Data will be resolved either at runtime or during lto.
  return MyClass::Data.some_member;
}

int bar() {
  std::array<float, MyClass::Data.some_member> some_array; // ERROR! Can't be resolved at compile time
}

So what's the point of being able to declare a static constexpr data member without its immediate initialization?

As long as you define it later inside a header before it's actually used, then everything is fine, which can sometimes come in handy. However, in your case, since you want to define the variable inside an implementation file, you create this two-tiered system where the member is constexpr in the implementation file, and const anywhere else.

All this to say: If all your delayed constexpr definitions are in implementation files like in your posted code, then just make them const, and use locally a constexpr in an anonymous namespace inside of the implementation file if you need it there. No need for the macro at all.

Upvotes: 2

Related Questions