Is `sizeof(T)` with an incomplete type a valid substitution-failure as per the C++ Standard? - c++

I've seen it come up a few times on StackOverflow and elsewhere that decltype(sizeof(T)) can be used with std::void_t to SFINAE off of whether T is complete or not. This process is even documented by Raymond Chen in Microsoft's blog titled Detecting in C++ whether a type is defined with the explicit comment stating:
I’m not sure if this is technically legal, but all the compilers I tried seemed to be okay with it.
Is this behavior reliable and well-defined as per the C++ standard?
The only indication I can find in the standard is from [expr.sizeof]/1 wherein it states:
... The sizeof operator shall not be applied to an expression that has function or incomplete type, to the parenthesized name of such types, or to a glvalue that designates a bit-field ...
However it is unclear to me whether the wording "shall not be applied" would imply that this is "invalid" for the purposes of substitution as per the rules in [temp], or whether this is ill-formed.
ℹ️ Note: This question is not directed at any particular version of the standard, but it would be interesting to compare if this has changed at any point.

"Shall not be applied" means that it would normally be ill-formed. In an SFINAE context, if something would normally be ill-formed due to resulting in "an invalid type or expression", this becomes a substitution failure, as long as it is in the "immediate context" (C++20 [temp.deduct]/8) and not otherwise excluded from SFINAE (e.g. see p9 regarding lambda expressions).
There is no difference between "invalid" and "ill-formed" in this context. p8 explicitly says: "An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments." This wording has been present since C++11. However, in C++03, invalid expressions were not substitution failures. This is the famous "expression SFINAE" feature that was added in C++11, after compiler implementers were sufficiently convinced that they would be able to implement it.
There is no rule in the standard that says that sizeof expressions are an exception to the SFINAE rules, so as long as an invalid sizeof expression occurs in the immediate context, SFINAE applies.
The "immediate context" has still not been explicitly defined in the standard. An answer by Jonathan Wakely, a GCC dev, explains the intent. Eventually, someone might get around to formally defining it in the standard.
However, the case of incomplete types, the problem is that this technique is very dangerous. First, if the completeness check is performed twice in the same translation unit on the same type, the instantiation is only performed once; this implies that the second time it's checked, the result of the check will still be false, because the is_type_complete_v<T> will simply refer to the previous instantiation. Chen's post appears to simply be wrong about this: GCC, Clang, and MSVC all behave the same way. See godbolt. It's possible that the behaviour was different on an older version of MSVC.
Second, if there is cross-translation-unit variance: that is, is_type_complete_v<T> is instantiated in one translation unit and is false, and is instantiated in another translation unit and is true there, the program is ill-formed NDR. See C++20 [temp.point]/7.
For this reason, completeness checks are generally not done; instead, library implementers either say that you are allowed to pass incomplete types to their templates and they will work properly, or that you must pass a complete type but the behaviour is undefined if you violate this requirement, as it cannot be reliably checked at compile time.
One creative way around the template instantiation rules is to use a macro with __COUNTER__ to make sure that you have a fresh instantiation every time you use the type trait, and you have to define the is_type_complete_v template with internal linkage, to avoid the issue of cross-TU variance. I got this technique from this answer. Unfortunately, __COUNTER__ is not in standard C++, but this technique should work on compilers that support it.
(I looked into whether the C++20 source_location feature can replace the non-standard __COUNTER__ in this technique. I think it can't, because IS_COMPLETE may be referenced from the same line and column but within two different template instantiations that somehow both decide to check the same type, which is incomplete in one and complete in the other.)

Related

Floating-point types as template parameter in C++20

According to cppreference C++20 now supports floating-point parameters in templates.
I am, however, unable to find any compiler support information on that site as well as on others.
Current gcc trunk just does it, the others are negative.
I would just like to know if this is a very low-priortity feature and/or when to expect it to become commonly supported.
The only related thing I can find is:
P0732R2 Class types in non-type template parameters. Kudos if anyone could briefly explain that instead.
It seems that the real question that can be answered here is about the history of this feature, so that whatever compiler support can be understood in context.
Limitations on non-type template parameter types
People have been wanting class-type non-type template parameters for a long time. The answers there are somewhat lacking; what really makes support for such template parameters (really, of non-trivial user-defined types) complicated is their unknown notion of identity: given
struct A {/*...*/};
template<A> struct X {};
constexpr A f() {/*...*/}
constexpr A g() {/*...*/}
X<f()> xf;
X<g()> &xg=xf; // OK?
how do we decide whether X<f()> and X<g()> are the same type? For integers, the answer seems intuitively obvious, but a class type might be something like std::vector<int>, in which case we might have
// C++23, if that
using A=std::vector<int>;
constexpr A f() {return {1,2,3};}
constexpr A g() {
A ret={1,2,3};
ret.reserve(1000);
return ret;
}
and it's not clear what to make of the fact that both objects contain the same values (and hence compare equal with ==) despite having very different behavior (e.g., for iterator invalidation).
P0732 Class types in non-type template parameters
It's true that this paper first added support for class-type non-type template parameters, in terms of the new <=> operator. The logic was that classes that defaulted that operator were "transparent to comparisons" (the term used was "strong structural equality") and so programmers and compilers could agree on a definition of identity.
P1185 <=> != ==
Later it was realized that == should be separately defaultable for performance reasons (e.g., it allows an early exit for comparing strings of different lengths), and the definition of strong structural equality was rewritten in terms of that operator (which comes for free along with a defaulted <=>). This doesn't affect this story, but the trail is incomplete without it.
P1714 NTTP are incomplete without float, double, and long double!
It was discovered that class-type NTTPs and the unrelated feature of constexpr std::bit_cast allowed a floating-point value to be smuggled into a template argument inside a type like std::array<std::byte,sizeof(float)>. The semantics that would result from such a trick would be that every representation of a float would be a different template argument, despite the fact that -0.0==0.0 and (given float nan=std::numeric_limits<float>::quiet_NaN();) nan!=nan. It was therefore proposed that floating-point values be allowed directly as template arguments, with those semantics, to avoid encouraging widespread adoption of such a hacky workaround.
At the time, there was a lot of confusion around the idea that (given template<auto> int vt;) x==y might differ from &vt<x>==&vt<y>), and the proposal was rejected as needing more analysis than could be afforded for C++20.
P1907R0 Inconsistencies with non-type template parameters
It turns out that == has a lot of problems in this area. Even enumerations (which have always been allowed as template parameter types) can overload ==, and using them as template arguments simply ignores that overload entirely. (This is more or less necessary: such an operator might be defined in some translation units and not others, or might be defined differently, or have internal linkage, etc.) Moreover, what an implementation needs to do with a template argument is canonicalize it: to compare one template argument (in, say, a call) to another (in, say, an explicit specialization) would require that the latter had somehow already been identified in terms of the former while somehow allowing the possibility that they might differ.
This notion of identity already differs from == for other types as well. Even P0732 recognized that references (which can also be the type of template parameters) aren't compared with ==, since of course x==y does not imply that &x==&y. Less widely appreciated was that pointers-to-members also violate this correspondence: because of their different behavior in constant evaluation, pointers to different members of a union are distinct as template arguments despite comparing ==, and pointers-to-members that have been cast to point into a base class have similar behavior (although their comparison is unspecified and hence disallowed as a direct component of constant evaluation).
In fact, in November 2019 GCC had already implemented basic support for class-type NTTPs without requiring any comparison operator.
P1837 Remove NTTPs of class type from C++20
These incongruities were so numerous that it had already been proposed that the entire feature be postponed until C++23. In the face of so many problems in so popular a feature, a small group was commissioned to specify the significant changes necessary to save it.
P1907R1 (structural types)
These stories about template arguments of class type and of floating-point type reconverge in the revision of P1907R0 which retained its name but replaced its body with a solution to National Body comments that had also been filed on the same subject. The (new) idea was to recognize that comparisons had never really been germane, and that the only consistent model for template argument identity was that two arguments were different if there was any means of distinguishing them during constant evaluation (which has the aforementioned power to distinguish pointers-to-members, etc.). After all, if two template arguments produce the same specialization, that specialization must have one behavior, and it must be the same as would be obtained from using either of the arguments directly.
While it would be desirable to support a wide range of class types, the only ones that could be reliably supported by what was a new feature introduced (or rather rewritten) at almost the last possible moment for C++20 were those where every value that could be distinguished by the implementation could be distinguished by its clients—hence, only those that have all public members (that recursively have this property). The restrictions on such structural types are not quite as strong as those on an aggregate, since any construction process is permissible so long as it is constexpr. It also has plausible extensions for future language versions to support more class types, perhaps even std::vector<T>—again, by canonicalization (or serialization) rather than by comparison (which cannot support such extensions).
The general solution
This newfound understanding has no relationship to anything else in C++20; class-type NTTPs using this model could have been part of C++11 (which introduced constant expressions of class type). Support was immediately extended to unions, but the logic is not limited to classes at all; it also established that the longstanding prohibitions of template arguments that were pointers to subobjects or that had floating-point type had also been motivated by confusion about == and were unnecessary. (While this doesn't allow string literals to be template arguments for technical reasons, it does allow const char* template arguments that point to the first character of static character arrays.)
In other words, the forces that motivated P1714 were finally recognized as inevitable mathematical consequences of the fundamental behavior of templates and floating-point template arguments became part of C++20 after all. However, neither floating-point nor class-type NTTPs were actually specified for C++20 by their original proposals, complicating "compiler support" documentation.

What does validity of a templated code mean?

Validity of a non-template code is more or less clear to everyone - a compiler checks the syntax, types, and rules. The compiler has everything it needs to perform these checks.
But when the compiler has to compile a templated code (for example STL, or Boost) it does't have enough information.
While the templated code isn't instantiated the compiler doesn't know what operations mean, whether or not they are defined for the types that will be used to instantiate this templated code, etc. Templated code can't be compiled untill instantiated.
Is there such a thing as a validity of a templated code? Would it require us to compile every instantiation of a templated code to verify its validity?
The standard talks about such validity:
The validity of a template may be checked prior to any instantiation. [ Note: Knowing which names are type names allows the syntax of every template to be checked in this way. — end note ]
As the note says, the reliable check is merely syntactic (or grammar-based). Even rules like looking up non-dependent names are covered by the rule that any template that cannot be instantiated is ill-formed, no diagnostic required. The implementation may then do anything at all, including compiling a program that does… something. (This is really too much implementation freedom; some of these rules could just be “may be ill-formed (with a diagnostic) at the implementation’s discretion”.)

Definition of "convertible to" used in ISO C++ library clauses

The response of LWG 484 shows "convertible" is "well-defined in the core". Which clauses does it refer to?
I have this question because currently the requirements of is_convertible are carefully defined in [meta.rel]/5, which are not in the core. Although it has clearly noted the technical benefits, the coexistence with other styles seems confusing in the overall library clauses. So there are more questions about the general use.
Do the meaning of requirements implied by is_convertible and the "convertible to" (without the code font) wording in the library clauses exactly the same?
If true, why both are used?
If not, is there any guideline or rationale to differentiate these subtle use cases?
Convertible to refers to something that can be a result of a standard conversion sequence defined in [conv] and since is_convertible is defined in terms of what conversion the core language allows during the copy-initialization of a return value, I don't see any difference.
Edit: Implicit conversion sequence might be another useful term not mentioned in the question. It is defined in [over.best.ics]
The "'convertible' is well-defined in core" statement was added at some point between 2004 and 2009, though that page doesn't say exactly when. So I looked at the C++03 and C++11 standards, and it appears that neither actually has a definition of the term "convertible". However, I think we can infer that "convertible" is generally intended to mean "implicitly convertible", and the meaning of "implicitly convertible" is given by [conv] ([conv.general] in newer editions of the standard).
For example C++11 [conv]/3 says:
An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t (8.5). ...
However, it should be noted that the definition of "implicitly converted" uses an expression, not a type, as the source, whereas "convertible to" in the library sometimes has a type as the source. In that case, "T is convertible to U" should be interpreted as "all expressions of type T can be implicitly converted to type U".
There are other plausible meanings of "convertible", for example:
The first sentence of [expr.static.cast] says "The result of the expression static_cast<T>(v) is the result of converting the expression v to type T". This is more powerful than an implicit conversion. It can, for example, convert void* to int*, or from an enum type to an integral type.
The sections of the C++ standard that specify the behaviour of (T) cast-expression and T ( expression-list(opt) ) are titled "Explicit type conversion (cast notation)" and "Explicit type conversion (functional notation)". These conversions are much more powerful than implicit conversions; they can call explicit constructors and explicit conversion operators, and even perform static_casts, reinterpret_casts, and const_casts (and even bypass access control in some cases).
But if we look at how the term "convertible" is actually used in the standard, it seems that it must be intended to mean "implicitly convertible". For example, C++11 [allocator.uses.trait]/1 says:
Remark: automatically detects whether T has a nested allocator_type that is convertible from Alloc. Meets the BinaryTypeTrait requirements (20.9.1). The implementation shall provide a definition
that is derived from true_type if a type T::allocator_type exists and is_convertible<Alloc, T::allocator_type>::value != false, otherwise it shall be derived from false_type. [...]
Evidently the intent here was that "convertible" means the same thing as what is_convertible tests for, that is, implicit convertibility (with a caveat that will be discussed below).
Another example is that in C++11 [container.requirements.general], Table 96, it is stated that for a container X, the type X::iterator shall be "convertible to X::const_iterator". The general understanding of this is that it means implicitly convertible, i.e., this is well-formed:
void foo(std::vector<int>& v) {
std::vector<int>::const_iterator it = v.begin();
}
Imagine if you had to use a cast, or even just direct-initialization notation, to perform this conversion. Could LWG have meant to use the word "convertible" to with a meaning that would make such an implementation conforming? It seems very doubtful.
Wait, does "convertible to" have the same meaning as what the standard type trait std::is_convertible tests for? Assuming that "convertible to" means "implicitly convertible to" with the caveat above, strictly speaking, no, because there is the edge case where std::is_convertible<void, void>::value is true but void is not implicitly convertible to itself (since the definition of a void variable is ill-formed). I think this is the only edge case (also including the other 15 variations involving cv-qualified void), although I'm not 100% sure about that.
However, this doesn't explain why std::is_convertible is sometimes used in the library specification. Actually, it is only used once in C++11 (other than in its own definition), namely in [allocator.uses.trait]/1, and there, it doesn't seem that there is any intent for it to mean anything other than "implicitly convertible", since it is irrelevant what happens when Alloc is void (as it is mentioned elsewhere that non-class types are not permitted as allocator types). The fact that std::is_convertible is even mentioned here seems like it's just a mild case of an implementation detail leaking into the specification. This is probably also the case for most other mentions that appear in newer editions of the standard.
C++20 introduced a concept called std::convertible_to, where std::convertible_to<From, To> is only modelled if
From is implicitly convertible to To, and
static_cast<To>(e) is well-formed, where e has type std::add_rvalue_reference_t<From> (i.e., From is also explicitly convertible to To via static_cast), and
some other requirements hold, which I won't get into here.
It's reasonable to ask whether some uses of "convertible to" actually mean std::convertible_to, given the similar names, and other considerations. In particular, the one mentioned by LWG 484 itself. C++20 [input.iterators] Table 85 states that if X meets the Cpp17InputIterator requirements for the value type T, then *a must be "convertible to T", where a has type X or const X.
This is almost certainly another case where "convertible to", as used in the library specification, implies "implicitly convertible to", but I think that it also should imply "explicitly convertible to", where the implicit and explicit conversions give the same results. We can construct pathological iterator types for which they don't. See this Godbolt link for an example.
I think both users and standard library implementers should feel free to assume that if a function accepts input iterators, it can assume that explicit conversions via static_cast or direct-initialization notation from *a to T can be used with the same result as implicit conversions. (Such explicit conversions may, for example, be used when calling function templates, if we want the deduced type of the function template specialization's argument to be T& or const T& and not some other U& or const U& where U is the actual type of *a.) In other words, I think "convertible to" in the input iterator requirements really means something like std::convertible_to, not std::is_convertible_v. I would be surprised if standard library implementations truly only relied on implicit convertibility in this case, and not both implicit and explicit convertibility (with the two being equivalent). (They can implement the library in an extremely careful way that only uses implicit conversions in cases where one would normally use an explicit conversion, but I'm not sure if they actually do. And even if they are indeed that clever, it is not reasonable to expect the same from anyone who is not a standard library implementer.)
But that's an issue for LWG to resolve. Notice that LWG 484 was reopened in 2009 and remains open to this day. It's probably a lot of work to go through all the uses of "convertible to" and "convertible from" and determine which ones should mean "implicitly convertible to" and "implicitly convertible from" and which ones should mean std::convertible_to (as well as if any uses of "implicitly convertible to" need to be changed to std::convertible_to).

Can adding 'constexpr' change the behaviour?

Given two programs where the only difference in the source code is the presence or absence of one constexpr, is it possible that the meaning of the program changes?
In other words, if there was a compiler option to ask the compiler to try really hard to infer constexpr where possible, would it break existing standard code and/or change its meaning in bad ways?
Imagine dealing with a codebase where the original developer forgot to include constexpr in places where it was possible, perhaps code written before C++11. It would be great if the compiler would infer constexpr to help you get on with your work. Of course, perhaps it should also warn about each time it does this inference, encouraging you to explicitly add the constexpr later. But it would still be useful. My worry is that it might break things?
So far, the only thing I can think of is that constexpr functions are implicitly inline and there can be situations where adding inline can change things in bad ways; for example if you break the one-definition-rule.
There is an easy trick:
template<int n>struct i{};
int foo(int){return 0;}
constexpr int foo(char){return 'a';}
template<class T=int, T x=1,i<foo(x)>* =nullptr>
bool bar(){return true;}
template<class T=int, T x=1,class...Ts>
bool bar(Ts...){return false;}
if int foo(int) is constexpr, a different overload of bar is chosen by default.
With different code running, any behaviour change can occur.
live example (simply change which #define X is commented out).
Design of the example:
The char overload prevents the above code from being ill-formed, no diagnostic required, as all templates must have a valid specialization. foo<char> supplies that. In practice, its existence is not required: ADL could find a foo from far away, overloaded on a some_type*, then pass some_type* as T. Which means no compilation unit could prove the code was ill-formed.
The Ts... makes that bar overload less-preferred. So if the first one matches, there is no ambiguity. Only if the first one fails to match (due to a SFINAE caused by foo(x) not being constexpr) does the second overload get called (or if, say, someone passed arguments to it).
Given two programs where the only difference in the source code is the
presence or absence of one constexpr, is it possible that the meaning
of the program changes?
Yes, this is at least true for constexpr functions. It is the reason why implementations are not allowed to choose which standard functions are marked constexpr, the main issue is that users may observe different behaviors via SFINAE. This is documented in LWG issue 2013: Do library implementers have the freedom to add constexpr? which says (emphasis mine):
Some concern expressed when presented to full committee for the vote
to WP status that this issue had been resolved without sufficient
thought of the consequences for diverging library implementations,
as users may use SFINAE to observe different behavior from otherwise identical code. Issue moved back to Review status, and will be
discussed again in Portland with a larger group. Note for Portland:
John Spicer has agreed to represent Core's concerns during any such
discussion within LWG.

Static Constant Class Members

Consider the following snippet:
struct Foo
{
static const T value = 123; //Where T is some POD-type
};
const T Foo::value; //Is this required?
In this case, does the standard require us to explicitly declare value in a translation unit? It seems I have conflicting information; boost and things like numeric_limits from the STL seem to do this sort of thing just like in my snippet.
OTOH, I remember reading somewhere (albeit a long long time ago) that you're still required to provide a declaration in a translation unit.
If this is the case, what about template specialization? Will each specialization require a declaration?
I'd appreciate your comments as to what the "right way" is.
You have to provide a definition in a translation unit too, in case you use the value variable. That means, if for example you read its value.
The important thing is that the compiler is not required to give a warning or error if you violate that rule. The Standard says "no diagnostic required" for a violation.
In the next C++ Standard version, the rule changed. A variable is not used when it is used as a constant expression. Simply reading value above where the variable is initialized directly in the class means that still no definition is required then.
See the definition of use in section 3.2 One Definition Rule of the Standard and requirement for a definition for static data-members in 9.4.2, paragraph 4 and 5 (in the C++98 Standard. Appears in paragraph 3 and 4 in the n2800 draft of the next Standard).
Correction: The rule already changed for c++03: If the variable appears where a integral constant expression is required, no definition is needed (quoting from an unofficial revisions list for the 2003 update), see resolution for this language defect report:
An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19), is the operand of the sizeof operator (5.3.3), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8)...
Note that even then, many uses are in cases where an integral constant is not required. Cases where one is, is in array dimensions or in template metaprogramming. So strictly speaking (see this report), only the c++1x solution provides really guarantee that in obvious cases also like "s == string::npos" where an integral constant is not required the definition of the static member is not needed, because the next Standard has a different, better wording of 3.2. This is however quite theoretical stuff, since most (all?) compiler don't moan anyway. Thanks for the guy in the comment section for telling me.
To add on to what litb said, from my copy of n2798:
9.4.2
[...]
2 The declaration of a static data member in its class definition is not a definition and
may be of an incomplete type other than cv-qualified void. The definition for a static
data member shall appear in a namespace scope enclosing the member’s class definition. In
the definition at namespace scope, the name of the static data member shall be qualified
by its class name using the :: operator.
You don't have to provide a definition for static integral constant members if you don't use them in some way that requires them to be stored in memory somewhere (e.g. take the address of such a member). See Stroustrup's The C++ Programming Language, section 10.4.6.2.
Edit:
Oops, I just re-read the question, and the question was for some type T. In general you would need to provide a definition, I agree. But if you used something from the int family, you wouldn't necessarily have to (with the caveat above).