cplusplus / CWG

Core Working Group
23 stars 7 forks source link

Defining static const member as const constinit outside class template while inside declaration contain constinit keyword valid or not #524

Closed ranaanoop closed 6 months ago

ranaanoop commented 6 months ago

Full name of submitter: Anoop Rana

Link to reflector thread (if any): https://stackoverflow.com/questions/78248691/static-data-member-of-template-class-type-constexpr-vs-const-constinit

Issue description:

Currently we can define a const static data member outside the class as constexpr as also noted in CWG 2800.

The problem is that all compilers seems to compile the following invalid(afaik) program, even though the declaration of ZeroVector inside the class template uses const constinit instead of just const. Demo

#include <array>

template<class T, std::size_t N>
    requires std::is_arithmetic_v<T> && (N >= 1)
class Vector 
{   
public:
    constexpr Vector() noexcept {}
    constexpr ~Vector() = default;
    //-----vvvvvvvvvvvvvvv-------------------->note this should be rejected but all 3 compilers accepts this
    static const constinit Vector ZeroVector;
};  
template<class T, std::size_t N> requires std::is_arithmetic_v<T> && (N >= 1)
const constinit Vector<T, N> Vector<T, N>::ZeroVector{};
int main()
{
    Vector<float, 7> boo = Vector<float, 7>::ZeroVector;
} 

The correct way to do this would be to remove the const constinit from the declaration that is inside the class and replace it with const. That is, the const constinit should only be in the definition outside the class as shown below:

#include <array>

template<class T, std::size_t N>
    requires std::is_arithmetic_v<T> && (N >= 1)
class Vector 
{   
public:
    constexpr Vector() noexcept {}
    constexpr ~Vector() = default;
    //-----vvvvv---------------------------NOTE ONLY THE CONST HERE
    static const Vector ZeroVector;
};  
template<class T, std::size_t N> requires std::is_arithmetic_v<T> && (N >= 1)
const constinit Vector<T, N> Vector<T, N>::ZeroVector{};
int main()
{
    Vector<float, 7> boo = Vector<float, 7>::ZeroVector;
}  

So, is snippet 1 supposed to be ill-formed or well-formed? Afaik snippet 1 is intended to be ill-formed and only snippet 2 is well-formed.

ranaanoop commented 6 months ago

Compilers do have some problem when using class template with const static data members that are later defined as constexpr. Another related question/thread: Why class templates don't allow const static data member to be defined outside the class as constexpr.

frederick-vs-ja commented 6 months ago

I'm not seeing any issue here except for those addressed in CWG2800.

It's clear in [dcl.constinit] p1 that constinit can be applied to a non-initializing declaration, so the type of the variable doesn't need to be complete at that point. On the other hand, [dcl.constexpr] p6 indicates that constexpr requires the declaration of variable to be initializing.

ranaanoop commented 6 months ago

It's clear in [dcl.constinit] p1 that constinit can be applied to a non-initializing declaration

@frederick-vs-ja dcl.constinit.sentence-2 seems to suggest the opposite to what you're saying. It says:

The constinit specifier shall be applied only to a declaration of a variable with static or thread storage duration. If the specifier is applied to any declaration of a variable, it shall be applied to the initializing declaration. No diagnostic is required if no constinit declaration is reachable at the point of the initializing declaration.

Note the emphasis on "initializing declaration" which means static const constinit Vector ZeroVector; in snippet 1 is invalid. Did you misread this perhaps?

frederick-vs-ja commented 6 months ago

@frederick-vs-ja dcl.constinit.sentence-2 seems to suggest the opposite to what you're saying. It says:

The constinit specifier shall be applied only to a declaration of a variable with static or thread storage duration. If the specifier is applied to any declaration of a variable, it shall be applied to the initializing declaration. No diagnostic is required if no constinit declaration is reachable at the point of the initializing declaration.

Note the emphasis on "initializing declaration" which means static const constinit Vector ZeroVector; in snippet 1 is invalid. Did you misread this perhaps?

This paragraph means that constinit can be applied to both initializing and non-initializing declarations of a variable. Once applied, it is mandatory for an initializing declaration, while being optional for a non-initializing one.

In other words, "it shall be applied to the initializing declaration" doesn't mean that "the declaration to which it is applied shall be initializing".

It was also said in P1143R2 that

After EWG discussion, constinit is permitted on any declarations of a variable; if it is present on some declaration but not on the initializing declaration, the program is ill-formed, no diagnostic required.

ranaanoop commented 6 months ago

@frederick-vs-ja I see. So both of the snippets in the original issue are well-formed.