Eligible special member functions and triviality - c++

Consider the following code:
#include <type_traits>
template<typename T>
concept Int = std::is_same_v<T, int>;
template<typename T>
concept Float = std::is_same_v<T, float>;
template<typename T>
struct Foo
{
Foo() requires Int<T> = default; // #1
Foo() requires Int<T> || Float<T> = default; // #2
};
static_assert(std::is_trivial_v<Foo<float>>);
For static assert to be satisfied, Foo<float> needs to be a trivial type, so, since it is a class type, be a trivial class. Thus, apart from being trivially copyable (which it is), it has to have an eligible default constructor (and all such constructors have to be trivial, class.prop#2), which is a special case of eligible special member function, defined by special#6:
An eligible special member function is a special member function for which:
the function is not deleted,
the associated constraints ([temp.constr]), if any, are satisfied, and
no special member function of the same kind is more constrained ([temp.constr.order]).
Rigorously, Foo<float> does not have an eligible default constructor, because constraints of #1 are unsatisfied and for #2 there's a more constrained default constructor (#1). Thus, Foo<float> is not a trivial class. However, the code above compiles successfully on gcc, clang and msvc (godbolt). Intuitively, it probably should, but then, it seems, the third clause of the definition above should be
no special member function of the same kind with satisfied associated constraints (if any) is more constrained ([temp.constr.order])
to allow #2 not to be "shadowed" by #1 (which would never be chosen during overload resolution for default constructor of Foo<float> anyway) and become eligible, making (due to being trivial and only one eligible) Foo<float> trivial.
So, is my analysis correct? If so, is this a compiler bug or the wording of the aforementioned clause is imprecise and it actually implies my version of it?

So, is my analysis correct?
Definitely. My intent in writing this wording in P0848 was very much that #2 is eligible - that's the one overload resolution would pick and it's not deleted, so it should be eligible.
I opened a CWG issue request which is now CWG 2595.

Related

Confusion (or Clang bug?) about incomplete types in std::vector

The C++20 standard states in [vector.overview]/4:
An incomplete type T may be used when instantiating vector if the allocator meets the allocator
completeness requirements. T shall be complete before any member of the resulting specialization of
vector is referenced.
The default allocator std::allocate does satisfy the allocator completeness requirements. The
main question is what "referenced" means in this context. The code I am confused about are variants of this:
#include <vector>
class MyClass;
class MyContainer
{
std::vector<MyClass> member;
};
class MyClass {};
int main()
{}
The above code compiles fine in all sorts of compilers. It still compiles if I explicitly default the default constructor:
#include <vector>
class MyClass;
class MyContainer
{
MyContainer() = default;
std::vector<MyClass> member;
};
class MyClass {};
int main()
{}
However, when I instead define the default constructor to be "empty", something weird happens. This is the code (here at Compiler Explorer):
#include <vector>
class MyClass;
class MyContainer
{
MyContainer() {};
std::vector<MyClass> member;
};
class MyClass {};
int main()
{}
With this code:
GCC 12, Clang 14 and Clang 15 in C++17-mode still compile this
Clang 15 in C++20-mode fails with this error:
In file included from <source>:1:
In file included from /opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/vector:64:
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_vector.h:367:35: error: arithmetic on a pointer to an incomplete type 'MyClass'
_M_impl._M_end_of_storage - _M_impl._M_start);
~~~~~~~~~~~~~~~~~~~~~~~~~ ^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_vector.h:526:7: note: in instantiation of member function 'std::_Vector_base<MyClass, std::allocator<MyClass>>::~_Vector_base' requested here
vector() = default;
^
<source>:7:5: note: in defaulted default constructor for 'std::vector<MyClass>' first required here
MyContainer() {};
^
<source>:3:7: note: forward declaration of 'MyClass'
class MyClass;
My first instinct, only looking at the Clang 15 error, was "clang is correct". The default
constructor does (implicitly) invoke the default constructor of std::vector<MyClass>, and the
standard says that you cannot reference members as long as MyClass is incomplete.
However, I'm pretty much sure that this cannot be the answer since:
All the other compilers (see above) do not even warn
The Thrift C++ compiler actually produces code that does exactly this.
So, my question is: Is this a Clang 15 bug? And if so, is (implicitly) invoking the default
constructor of std::vector<MyClass> not considered a "reference" in terms of [vector.overview]/4?
I did search the LLVM bug tracker for the terms "vector" and "incomplete", but that did not turn
something up, so if this is a known bug, it's not known in the context of std::vector, I guess.
Edit 1: I don't think this is a duplicate
This was closed as a duplicate of two questions, which in my opinion, is incorrect. The differences are subtle but relevant. The two questions are:
std::map::reverse_iterator doesn't work with C++20 when used with incomplete type
The std::map specification never says (contrary to the std::vector specification, see [vector.overview]/4) that it can be instantiated with an incomplete type. Thus, the problem in my question (with std::vector) is not the same as the problem with std::map
In the question, an object of the offending std::map is instantiated before the incomplete type was made complete - this can obviously never work. In my question, no object is instantiated at all.
What C++20 change to reverse_iterator is breaking this code?
Here, a member of std::vector<incompleteType> is explicitly referenced (namely ::reverse_iterator). This is obviously not covered by [vector.overview]/4. However, I'm not doing that in my question.
Again, an object of the vector-with-incomplete-type is instantiated before the type was made complete, which cannot work and which I'm also not trying to do.
It's true that "referenced" is not clear here. What I think this sentence probably means is that T shall be complete before you do anything that would require the definition of any member of the resulting specialization to exist.
In the second example
class MyClass;
class MyContainer
{
MyContainer() = default;
std::vector<MyClass> member;
};
class MyClass {};
the definition of std::vector<MyClass>'s default constructor is not needed until the compiler actually implicitly defines the default constructor of the enclosing class, MyContainer. And that doesn't happen until the first time MyContainer's default constructor is either odr-used or needed for constant evaluation, per the last sentence of [dcl.fct.def.default]/5:
A non-user-provided defaulted function (i.e. implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) or needed for constant evaluation ([expr.const]).
Your first example, where you didn't declare any default constructor, and your second example, where you declared it as defaulted inside the class definition, are treated similarly: in both cases the constructor is non-user-provided, so it does not get eagerly defined. In both examples, MyContainer has a non-user-provided copy constructor, move constructor, copy-assignment operator, move-assignment operator, and destructor, which likewise are not defined until their definitions are needed, so the program avoids "referencing" any members of std::vector<MyClass>.
In the third example, because the constructor is user-provided, it is defined even if it is never used, and its definition implicitly calls the default constructor of std::vector<MyClass>, i.e., the latter is "referenced".
When you break the rules, as you did in the third example, the program has undefined behaviour. I understand that it seems awfully unfriendly that most compilers you tried don't even warn you, but there is a reason why diagnostics are not required when you misuse incomplete types: it's difficult for templates to check for completeness in a way that does not cause bigger problems (though I believe there is ongoing work on this issue in Clang).

I'm reading "C++ Templates: The Complete Guide" and there is something that I don't understand

It says:
A function generated from a function template is never equivalent to an ordinary function even though they may have the same type and the same name. This has two important consequences for class members:
A constructor generated from a constructor template is never a default copy constructor.
Firstly, I don't understand how that conclusion came from the first statement.
Secondly, when I try to simulate that conclusion, it gives me an error that it cannot be overloaded, so I don't understand the outcome either.
Edit: the code I used
#include <iostream>
template<class T>
class entity
{
private:
int x;
public:
entity();
entity (int const& y):x(y)
{
printf("non-template");
}
entity (T const& y): x(y)
{
}
};
int main()
{
entity<int> en(5);
}
A copy constructor is a non-template constructor with a certain signature. A specialization of a constructor template is never (by definition) a copy constructor.
I think "consequences" is not really used here to mean a logical implication immediately following from the previous statement. I think it is rather just meant to demonstrate some examples where the differentiation between an ordinary function and a function specialized from a function template matters.
This use of the word "consequence" seems to be a linguistic oddity. I sometimes see it used like this. It really seems to mean something more like "this allows for the following (and the following is true)".
The point here is that only declaring a copy constructor (meaning a non-template constructor) can inhibit the declaration of the implicit copy constructor. So if you only declare a constructor template that can be specialized to have the same signature as a copy constructor, there will still be an implicit copy constructor.
I don't know though what the author means with "default" in "default copy constructor". I would usually guess they just used an informal variant for "implicit", but that wouldn't make sense in the context.
In your example code there is no constructor template. The declaration of a template begins with template</*...*/> (or since C++20 as a function with auto placeholders in parameter types).
entity (T const& y): x(y)
{
}
This is just an ordinary non-template constructor. It's parameter type does depend on the template parameter of the class, but that doesn't make it itself a template (although a templated entity). Even setting that aside, if you specialize the class for e.g. entity<int>, then the parameter becomes type int const&, not entitiy<int> const&, so it won't have the signature of a copy constructor at all. The compiler complains about impossible overload because there is already another constructor with exactly the same parameter list and both are non-template constructors.
A constructor template would be e.g.:
template<typename U>
entity (U const& y): x(y)
{
}
and this one would not inhibit the implicit declaration and definition of the implicit copy constructor although it can be specialized to entity(entity<T> const&) for U = entity<T>. Overload resolution will then still choose the implicit copy constructor instead of this constructor template to perform copies.

Must destructor be just available (public) or fully valid for default initialized class members?

Please consider a struct A having a field u of type U<R> with a default initializer. The destructor ~U<R> is only declared:
template<typename T>
struct U {
~U();
};
struct R;
struct A {
U<R> u = U<R>{};
};
All compilers accept this code, demo: https://gcc.godbolt.org/z/oqMjTovMo
But if we define the destructor ~U<R> as follows:
template<typename T>
struct U {
~U() { static_assert( sizeof(T) > 0 ); }
};
then the current compilers diverge. MSVC keeps accepting the program, while GCC/Clang print the error
error: invalid application of 'sizeof' to an incomplete type 'R'
demo: https://gcc.godbolt.org/z/713TzPd6v
Obviously, the compiler must verify destructor availability of default initialing class members in case an exception occurs during construction. But does the standard require that the compiler just check the availability of the destructor (as MSVC does), or the compiler should verify its body as well? MSVC behavior looks more convenient here since it permits forward declaration of R by the moment of struct A definition.
P. S. This questing has not only purely theoretical interest. If one replaces U here with std::unique_ptr then it explains why class fields of type std::unique_ptr<R> are accepted by MSVC and rejected by GCC/Clang for incomplete classes R.
[dcl.init.aggr]/8: (emphasis mine)
The destructor for each element of class type is potentially invoked ([class.dtor]) from the context where the aggregate initialization occurs.
[basic.def.odr]/8:
A destructor for a class is odr-used if it is potentially invoked.
[basic.def.odr]/10:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required.
So ~U() is potentially invoked at U<R> u inside A, which requires its definition.
Then according to [temp.inst]/4:
Unless a member of a class template or a member template is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist ...
Bonus note: and of course, it is actually invoked whenever an instance of A is destroyed. So it needs to be compiled.
Note: in the first version the compilers accept the code because the destructor is not inline, so it fails during linking (example).
As for MSVC accepting the code, it appears to be a bug.

is_constructible and is_destructible unaffected by friend declarations

Clang and GCC appear not to honor friend declarations when evaluating std::is_constructible and std::is_destructible.
Regarding `is_constructible, cppreference.com says:
Access checks are performed as if from a context unrelated to T and any of the types in Args. Only the validity of the immediate context of the variable definition is considered.
(The site doesn't explain how is_destructible deals with access checks, but access modifiers do affect the behavior of is_destructible in general, so I'd expect it to work the same way as is_constructible.)
Therefore, it seems to me that this code should not compile, since in the immediate context of the check the constructor and destructor are available, as evidenced by the local variable instantiation:
class Private
{
Private() {}
~Private() {}
friend class Friend;
};
class Friend
{
public:
Friend()
{
// Both of these should fire, but they do not.
static_assert(
!std::is_constructible<Private>::value,
"the constructor is public");
static_assert(
!std::is_destructible<Private>::value,
"the destructor is public");
// There is no error here.
Private p;
}
};
...but Coliru compiles it without error (using either GCC or Clang).
Is this a bug (or at least a nonconformity) in both compilers, or is cppreference.com misrepresenting the standard, or am I misunderstanding cppreference.com's statement?
This is exactly what
Access checks are performed as if from a context unrelated to T and
any of the types in Args.
says. "A friend of T" is by definition not "unrelated to T".
"immediate context" is a term of art, but in any event the sentence is talking about the immediate context of the hypothetical variable definition, not the use of is_constructible.
It would be madness to make the is_constructible check context-dependent; that would mean that the same type, is_constructible<T, Args...>, has different base classes in different contexts.

Error when checking if a tuple of references is default constructible

With g++-5 I get the following output
#include <type_traits>
#include <tuple>
int main()
{
bool b;
b = std::is_default_constructible<int>::value; //Compiles, returns true
b = std::is_default_constructible<int&>::value; //Compiles, returns false
b = std::is_default_constructible< std::tuple<int> >::value; //Compiles, returns true
b = std::is_default_constructible< std::tuple<int&> >::value; //Does not compile
}
Is this a bug in is_default_constructible's implementation ?
The error message is a long stack list ending in:
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/tuple:105:9: error: reference to type 'int' requires an initializer
: _M_head_impl() { }
This is not a bug in is_default_constructible. That type trait is only required to check the immediate context of default construction, it doesn't have to deeply evaluate any member initializers. This restriction is probably so that it can be implemented without dedicated compiler magic by using SFINAE. (see [meta.unary.prop], esp. p7).
The tuple and pair default constructors were not required to fail in the immediate context (SFINAE-friendly) if the element type can't be default-constructed. This has been addressed by LWG 2367, which introduces the following SFINAE requirement for the tuple default constructor:
Remarks: This constructor shall not participate in overload resolution
unless is_default_constructible<Ti>::value is true for all i. [...]
With this additional requirement, default construction of a tuple must fail in a SFINAE-friendly way, such that is_default_constructible now works for tuple if the elements fail to be default-constructed in the immediate context (which is the case for reference types).
LWG 2367 is currently in Ready status; the proposed resolution has not (yet) been incorporated into the github drafts.
[-- this part is still under consideration
Yakk raised an important point in the comments: Why does is_default_constructible have to deeply instantiate the member initializers?
As far as I can tell, this has to do with the conditional constexpr'iveness of tuple's default constructor. is_default_constructible causes the instantiation of the default constructor. It only needs to instantiate the declaration in order to determine whether or not this constructor can be called without failures in the immediate context. However, the instantiation of the declaration requires determining the constexpr'iveness, and this causes the instantiation of the definition of the constructor.
A member function (or constructor) of a class template which has been marked as constexpr is only conditionally constexpr: only the member functions of those class template instantiations will be constexpr where the body doesn't violate the constexpr restrictions. This requires the instantiation of the body of the constructor, for constructors in order to check if the member initializers are allowed inside a constexpr function. Consider:
struct nonconstexpr { nonconstexpr() { std::cout << "runtime\n"; } };
struct isconstexpr { constexpr isconstexpr() {} };
template<typename T>
struct wrapper { T t; constexpr wrapper() : t() {} };
When instantiating the default ctor of wrapper, the compiler has to instantiate the member initializers in order to determine whether or not this instantiation shall be constexpr.
In the case of std::tuple, this causes somewhere the instantiation of a member-initializer which tries to value-initialize the reference tuple leaf (data member). This is an error, and it does not occur within the immediate context of the original instantiation of the default constructor. Therefore, it is a hard error rather than a Substitution Failure in the immediate context.
--]
This part isn't entirely clear to me because CWG 1358 essentially made all instantiations constexpr, whether or not they actually satisfy the criteria. And indeed, gcc 6.0 does not fail to compile the following example, while gcc 5.1 and clang 3.7 reject it:
#include <type_traits>
template<typename T>
struct foo
{
T t;
constexpr foo() {} // remove `constexpr` to make it compile everywhere
};
int main()
{
static_assert(std::is_default_constructible<foo<int&>>{}, "!");
}
CWG 1358 also tells us why the distinction between the two approaches - conditional constexpr and constexpr despite violations - is important:
Questions arose in the discussion of issue 1581 as to whether this
approach — making the specialization of a constexpr function template
or member function of a class template still constexpr but unable to
be invoked in a constant context — is correct. The implication is that
class types might be categorized as literal but not be able to be
instantiated at compile time. This issue is therefore returned to
"review" status to allow further consideration of this question.
For libc++, there is bug #21157, which has been resolved on 2014-10-15 and appears in the clang3.6 branch. For libstdc++, there doesn't seem to be a bug report; the issue was fixed in a combined commit on 2015-06-30 which also implements N4387 - Improving Pair and Tuple (Revision 3) which currently does not seem to appear in any gcc5 branches.