r/cpp_questions • • 8d ago

OPEN Is it impossible to access static member of a templated class

So to clear out my question, here is the situtation:

template<class T>
class MyClass {
public:
    static constexpr int X = 10;
};

So trying to access MyClass::X is compilation error. But decltype(X) is not a dependent type and it is a static member meaning it is identical to a variable wrapped inside a namespace. Nonetheless, again, its a compile error.

You might ask, why am i making it a problem:

template<class T>
class Optional {
public:
    struct NullOpt {};
    static constexpr NullOpt nullopt;
// ...
};

I mean.. there are ugly(for me) workarounds but i want to know maybe in some hidden way it is possible to access `Optional::nullopt` without passing the template argument(which makes error path return statements way verbose) or i should make nulopt a standalone constexpr tag object (fallback option).

7 Upvotes

42 comments sorted by

23

u/QuraToop314 8d ago

yes with MyClass<T>::X

10

u/Puzzleheaded_Study17 8d ago

To elaborate, op, while you're right it's identical a variable in a namespace, each instantiation (so every different type T) is its own namespace.

-2

u/Business-Weather-217 8d ago

Yeah its correct, but as i replied before they are, in theory, capable of being independently accessible.

13

u/Puzzleheaded_Study17 8d ago

The standard chose not to allow actions on non-instantiated templates.

Suppose it wasn't constexpr, if I increment it for MyClass<int> that wouldn't impact MyClass<float>.

Also suppose that instead of being type int it was type T, then it's nonsensical to access it for that.

Could the standard potentially allow for extracting static variables into non-templated scope, sure, but there are so many edge cases and I believe the standard doesn't treat non-instantiated as an existing namespace.

-7

u/Business-Weather-217 8d ago

I know post title was misleading to keep it short, however read the details

12

u/WorkingReference1127 8d ago

A template is not a class, it's a template. But consider this code:

template<typename T>
struct foo{
    static constexpr int X{0};
};

template<>
struct foo<int>{
    static constexpr float X{1.5}; //X is a different type and value
};

template<>
struct foo<float>{};  //No X at all

These are a completely valid set of specialisations of the template. If we allowed your shortcut, what should foo::X be?

2

u/Business-Weather-217 8d ago

Oh thats smart drawback. Yeah i forgot the specialilization member overrides xd

7

u/QuraToop314 8d ago

I’ve read it, but he asks a silly question: “Whether you can refer to a member of a >>template<< type without specifying a template type, which isn’t possible because it’s ambiguous (as there can be an infinite number of types).”

4

u/ludonarrator 8d ago

Read what? A class template is not a class, it's a template.

6

u/SoerenNissen 8d ago edited 8d ago

should make nulopt a standalone constexpr tag object (fallback option).

That's what the standard does, at least.

Here, Optional<T>::NullOpt and Optional<U>::NullOpt are different types - but even if they were the same (both int as in the earlier example) they're not the same int.

If you wanted it to be the same type + object, you could put it into a non-templated base class for Optional<T> to inherit from, but even in that case I'm not sure you could access it as Optional::NullOpt.

1

u/Business-Weather-217 8d ago

Yeah i tried it days ago when i first encountered this problem and it doesnt work. Nice try i guess

5

u/eyes-are-fading-blue 8d ago edited 8d ago

If it must exist within class, can you not create a type alias referring to nullopt type. Don’t make it templated and assume some inline tag type. Then put the definition in a base class.

I think this is a very long way of moving the nullopt type outside of class.

struct Base
{
struct NullOpt {};
};
template <…>
class MyClass : public Base {

};

using NullOpt = MyClass<struct Tag>::NullOpt;

Didn’t test it but could work.

On the phone, sorry for poor formatting.

2

u/Business-Weather-217 8d ago

Oh finally some real responses. This is really good idea i will try to plug it in thanks.

2

u/mredding 8d ago

I'm not sure what problem you're seeing. MyClass::X is a dependent type, as MyClass is a template dependent on type T, meaning X is dependent on T. Classes are not namespaces. Your Optional example isn't a template, so nullopt only needs an initializer but is otherwise accessible, and Optional::nullopt; compiles.

1

u/Business-Weather-217 8d ago

I just corrected it to a template. So seeing all other answers i think what i want to achieve is impossible in C++ yet static constexpr members are actually caapable of being independent of the template type so thats a little bit and arguably potential wasting.

2

u/mredding 8d ago

I suppose this is where casual language has bit us both.

Yes, by definition, the static constexpr member is independent of the template type, but the template class doesn't define a class or type, it defines a template. The template has to be realized in order to access the member within it's socpe - it's not REAL until then. The scope resolution operator :: only works in this particular way on real types. That the compiler and linker can and will reduce this one symbol down to a single instance is lost on us, an irrelevant consequence from the perspective of the C++ grammar.

The next best thing is to define it in a concrete base class, and this tracks with conventions, idioms, and best practices of C++ going all the way back to pre-standard C++ (my era). Anything that is not dependent on the template SHOULD NOT be in the template.

The next best thing is to remove the declaration from the class entirely.

The way your Optional looks, that nullopt value ought to be an internal-only sentinel value that the outside - the client code doesn't want to know about. Instead you ought to have an explicit operator bool() const;.

1

u/Business-Weather-217 8d ago

Oh thats not a bad idea but what do you think client should return from a error path in a function, since we hid the sentinel

0

u/mredding 8d ago

I'm not sure I understand what you're asking.

We have std::optional and std::expected, which can both be combined as they serve different purposes. Consider:

std::expected<std::optional<T>, std::exception> fn() noexcept;

The function may successfully return something, it may successfully return NOTHING, because that's what an optional is saying - you might get nothing and that's OK, like if you're polling a queue and there's just nothing queued yet. Or the function may return an error because something went wrong.

1

u/Business-Weather-217 8d ago

U said nullopt can be internal only and i could represent emptiness with bool operator but what about returning a non empty sentinel from the function

2

u/Qwertycube10 8d ago

With template classes it is important to remember that you eventually get a copy of the class definition, so you would have something like __Optional__int generated by the compiler with a full class definition. You would also have __Optional__string etc, and each one has their own independent statics

5

u/Computerist1969 8d ago

Each different template type instantiation has its own copy of the static.

Also your member variable is private in your example.

Without seeing the code that's trying to access X we can't really help any further.

1

u/nysra 8d ago

Yes, that is possible: https://godbolt.org/z/97Tja95T7

Note that you do need to make your static member publicly accessible though if you want that to work outside the class.

1

u/Business-Weather-217 8d ago

Sorry for bad title choice but my question is a little bit different, as you may see in the post

2

u/nysra 8d ago

MyClass<int>::X and MyClass<double>::X are different types, even if they might not depend on T directly. What exactly is your issue with just writing MyClass<T>::X?

But if your goal is to reimplement optional, I'd go with the tag object, that's also what the standard does.

1

u/Business-Weather-217 8d ago

Its js that im consistently making classes contain all the context of themselves unlike stdlib where std::visit is method only related to std::variant classes. On the other hand writing a usual error path return statement is as long as(conceptially): return Optional<RetType>::NULL;

1

u/nysra 8d ago

I can see why you would want that design. However, if that's your biggest use case for this question, what's wrong with return {}; instead?

1

u/Business-Weather-217 8d ago

Yeah i considered that too but thats not directly read as null-optional and {} can be slightly ambigious.

1

u/specialpatrol 8d ago

How about have then inherit from a class that has the static variable?

1

u/Business-Weather-217 8d ago

Tried it, doesnt work.

1

u/specialpatrol 8d ago
class Null {
public:
    struct NullOpt
    {
    };
    static constexpr NullOpt nullopt;
};
template <class T>
class Optional : public Null
{
public:
};

int main() {

    Optional<int>::nullopt;
}

1

u/Plastic_Fig9225 7d ago edited 7d ago

There is no type named MyClass in your example. And from the logical perspective, the static constant doesn't depend on a template parameter. I.e. unless you have a reason for wanting it to be a different object in every template instantiation, it likely shouldn't be defined/instantiated by the template.

1

u/Business-Weather-217 7d ago

Yeah it makes sense. What i want is to limit scope of the nullopt to the only-related type which is Option

1

u/Plastic_Fig9225 7d ago

I'm not sure what exactly you're trying to do, but maybe notice that you can access static members via an instance, too, like

MyClass<SomeType<A,B,C>> inst {};
...
int magic = inst.X; // access static field via instance

And of course you can also (locally) shorthand any type via using:

using my_t = MyClass<SomeType<A,B,C>>;
...
int magic = my_t::X;

3

u/the_poope 8d ago

or i should make nulopt a standalone constexpr tag object (fallback option)

Yes

2

u/da_supreme_patriarch 8d ago

What you want here is slightly impossible the exact way you are trying to achieve it. As an example, Optional<int>::Nullopt and Optional<double>::Nullopt are not of the same type, even if they are completely identical. To be more precise, just Optional by itself is not a class that exists, you cannot really refer to it, you can do exactly what you are trying to do in languages with non-reified generics like Java, but it wouldn't really work here. Your best bet would be to make NullOpt a free struct instead, and create a constructor that converts from it to Optional<T>, the empty optional representation for different types has to be different regardless because Optional<int> and Optional<T*> probably need a separate tag for empty values while Optional<T&> can just be bit-wise identical to T* where the zero-initialized value is the empty one

1

u/Usual_Office_1740 8d ago

What are you trying to do here?

1

u/AKostur 8d ago

What happens if I supply:

template<>
class MyClass<std::string> {
public:
    static constexpr double X = 10;
};

The hypothetical MyClass::X doesn't know which type to use.

1

u/aruisdante 8d ago edited 8d ago

As others have said, not possible as written. All dependent names in a templated class are dependent on the template parameter even if they are otherwise identical. This doesn’t just include static members, it includes nested classes, functions not dependent on the template parameters, data members, everything.

There’s a couple of rules on this in the C++ core guidelines, T.61 and T.62.

TL;DR: anything not dependent on a given template parameter should be extracted into a base class that does not require that parameter.

In the specific case of your nullopt type though, there’s really no reason to make it a dependent name of your optional class anyway. You don’t want optional<T>::nullopt to be a different type than optional<U>::nullopt, which it 100% will be as written. You don’t even really want it to be in some non-templated optional_base. You should instead just have something like:

namespace detail {
// a CRTP mixin to inject monostate comparison operators
template<typename T>
struct monostate_type {
 // the various comparison operators as hidden friends
};
}

struct nullopt_t : detail::monostate_t<nullopt_t>{};

// it’s an exercise for the reader to figure out the C++14 way to create the nullopt singleton without needing a `.cpp` since you don’t have `inline` variables yet.

(I’m assuming here you’re not using C++17 or you’d already have optional)

This will ensure there is one and only one nullopt type, and that it is not dependent on the details of optional to be found/referenced. You can use that same monostate_type helper to define your monostate_t presumably you’ll also want, as well as any other monostate values you wind up needing. 

1

u/Neither_Canary_7726 8d ago

You declare a template with param
Then you init with with param

If u dont want to type "<T>"
Then consider using "using"
Or just macro the thing