How do I delay the instantiation of a static data member in Visual C++?
Asked Answered
C

1

8

The following code works with GCC and Clang, but not with Visual C++:

#include <type_traits>

struct MyType {
    static constexpr std::size_t it = 10;
};

struct MyType2 {
};

template<typename T>
struct Type2 {
    static constexpr std::size_t it = T::it;
};

int main() {
    Type2<MyType> t1;
    Type2<MyType2> t2; // Visual C++ complains that MyType2::it doesn't exist
    (void) t1;
    (void) t2;
}

According to section 14.7.1 of the standard:

... the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist

So it seems that this is a bug in Visual C++; it should be accepting this code.

Regardless, I still want to be able to do this using Visual C++. What is the easiest way to allow Visual C++ to work, without changing the syntax of accessing the member variable? I also require that Type2<T>::it doesn't exist when T::it doesn't exist, or that it is otherwise possible to SFINAE off of the existance of Type2<T>::it.


This changes the syntax, so I don't want it:

template<typename T>
struct Type2 {
    template<typename = void>
    static constexpr std::size_t it = T::it;
};

// Type2<MyType>::it<>

This is a lot of work, because my class will contain more than a simple constexpr variable:

template<typename T, typename = void>
struct Type2 {
};

template<typename T>
struct Type2<T, decltype((void) T::it)> {
    static constexpr std::size_t it = T::it;
};
Culbertson answered 25/5, 2017 at 7:14 Comment(2)
This appears to work: coliru.stacked-crooked.com/a/1fd7999053358d43Intermigration
You are reading too much into the standard. It doesn't say that invalid initializers are allowed if the member is never used. E.g. it = (abort(), 0) is much different from your example because it produces a run time error and not a compile time error.Rallentando
R
2

You could inherit it (when it exists) instead of declaring it directly:

template<typename T, typename = void>
struct ItHolder
{};

template<typename T>
struct ItHolder<T, decltype((void) T::it)> {
    static constexpr std::size_t it = T::it;
};


template<typename T>
struct Type2 : ItHolder<T> {
};

In other words, just take what you already suggested and combine it using another layer of indirection.

Roguery answered 25/5, 2017 at 7:19 Comment(3)
@Culbertson That's a requirement you should add to the question, then. It's not clear at all from its current wording.Roguery
@Culbertson The solution was still easy to adapt, though. Answer updated.Roguery
This is very nice. Compared to reversing the inheritance (having a Type2Base that contains the normal implementation, and Type2 is almost as in the question), I prefer it this way. For one, it makes it easy to extend the implementation for multiple members, and it also makes it easier to keep the template parameters of my intended type the same as I originally intended.Culbertson

© 2022 - 2024 — McMap. All rights reserved.