I don't see any reason to believe that the program in question is ill-formed. Simply having something in the code depend on the completeness of a type, then having something else later on depend on the completeness of the same type where the type has since been completed, does not violate the standard.
A problem arises if we have something like
inline Something something; // external linkage
inline void foo() {
auto& [one] = something;
}
defined in multiple translation units, where, in some of those, std::tuple_size<Something>
is already complete at the point where foo
is defined, and in others, it isn't. This seems like it should definitely violate the ODR, since the entity one
receives different types in different copies of foo
, however, I can't actually find a place in the standard that says so. The criteria for the multiple definitions to be merged into one are:
each definition of D shall consist of the same sequence of tokens; and
in each definition of D, corresponding names, looked up according to 6.4, shall refer to an entity defined
within the definition of D, or shall refer to the same entity, after overload resolution (16.3) and after
matching of partial template specialization (17.8.3), except that a name can refer to
and
in each definition of D, corresponding entities shall have the same language linkage; and
- in each definition of D, the overloaded operators referred to, the implicit calls to conversion functions,
constructors, operator new functions and operator delete functions, shall refer to the same function, or
to a function defined within the definition of D; and
- in each definition of D, a default argument used by an (implicit or explicit) function call is treated as if
its token sequence were present in the definition of D; that is, the default argument is subject to the
requirements described in this paragraph (and, if the default argument has subexpressions with default
arguments, this requirement applies recursively) 28 ; and
- if D is a class with an implicitly-declared constructor (15.1), it is as if the constructor was implicitly
defined in every translation unit where it is odr-used, and the implicit definition in every translation
unit shall call the same constructor for a subobject of D.
If there's a rule here that makes my code ill-formed, I don't know which one it is. Perhaps the standard needs to be amended, because it cannot have been intended that this was allowed.
Another way to make the program ill-formed NDR involves the use of a template:
template <int unused>
void foo() {
auto& [one] = something;
}
// define tuple_element and tuple_size
foo<42>(); // instantiate foo
This would run afoul of [temp.res]/8.4, according to which
The program is ill-formed, no diagnostic required, if ... the interpretation of [a construct that does not depend on a template parameter] in [the hypothetical instantiation of a template immediately following its definition] is different from the interpretation of the corresponding construct in any actual instantiation of the template