Reputation: 1870
I was surprised to discover that std::is_default_constructible
appears to ignore friend access. When declaring a default constructor private in a class and then friending a function, I'd expect that std::is_default_constructible
would return true.
Example: I ran the following on Wandbox: https://wandbox.org/ using Clang 5.0.0 and GCC 7.2.0 under C++17.
#include <type_traits>
#include <cassert>
class PrivateConstructor
{
private:
PrivateConstructor() = default;
friend void doIt();
};
void doIt()
{
bool isConstructible = std::is_default_constructible<PrivateConstructor>::value;
PrivateConstructor value;
assert(isConstructible); // FAILS!
}
int main(int,char**)
{
doIt();
return 0;
}
This code compiles but the assertion fails. Is the defined explicitly in the standard or is this a possible compiler bug?
Upvotes: 2
Views: 927
Reputation: 5339
Depending on your exact context and your need you might be able to get away with something like this (with T
being the type in question):
class DerivedT : public T {};
static_assert(std::is_default_constructible_v<DerivedT>);
If T
has a default constructor that is protected
(or public
, of course) the DerivedT
will be default constructible and static_assert
will pass.
The private
case requires even more "trickery". If the default constructor is private but you want to know if it exists, I presume you may have some other means to invoke it somehow (a friend
?), and perhaps similar means should be applied to DerivedT
to simulate this as well.
However, the private
case is also riskier. I would expect some legacy code (when = delete
wasn't available) may be using private
default constructor (preferably also without defining it - only declaration) to actually prevent default construction and make a clear statement of it (rather than relying on the compiler disallowing it implicitly).
Upvotes: 0
Reputation: 343
I think there are several points to summarize:
GameSalute tested with Clang and GCC. To show you a bit more what this can lead to I've done some experiments with MSVC, using Visual Studio 2017 V15.9.11 (currently the most recent version) in C++14 mode:
#include <memory>
#include <type_traits>
class NoFriend
{
private:
NoFriend() = default;
};
class Friended
{
friend struct std::is_default_constructible<Friended>;
//friend constexpr bool std::is_default_constructible_v; // C2433: 'std::is_default_constructible_v': 'friend' not permitted on data declarations
// C2063: 'std::is_default_constructible_v': not a function
//friend constexpr bool std::is_default_constructible_v = __is_constructible(Friended); // C2433: 'std::is_default_constructible_v': 'friend' not permitted on data declarations
// C1903: INTERNAL COMPILER ERROR in 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\bin\HostX64\x86\CL.exe'
//friend bool __is_constructible(Friended); // Non portable attempt:
// C2059: syntax error: '__is_constructible'
// C2238: unexpected token(s) preceding ';'
private:
Friended() = default;
};
class Tester
{
public:
void TestNoFriend() const
{
constexpr bool my_is_default_constructible_v = std::is_default_constructible<NoFriend>::value;
static_assert(std::is_default_constructible<NoFriend>::value == false, "NoFriend is default constructible");
static_assert(std::is_default_constructible_v<NoFriend> == false, "NoFriend is default constructible");
static_assert(my_is_default_constructible_v == false, "NoFriend is default constructible");
}
void TestFriended() const
{
constexpr bool my_is_default_constructible_v = std::is_default_constructible<Friended>::value;
static_assert(std::is_default_constructible<Friended>::value == true, "Friended is not default constructible");
//static_assert(std::is_default_constructible_v<Friended> == true, "Friended is not default constructible"); // C2338
static_assert(my_is_default_constructible_v == true, "Friended is not default constructible");
}
};
What you can see from this snippet:
So all I can recommend is to in fact stay right away from friending in namespace std. All that's being based on such a construct is non portable and in today's compilers also partially broken and inconsistent (at least for MS). Anyway, as asking for a private default constructor is rather nonsensical (except maybe if you're implementing a super-smart boost serializer class that needs non-intrusive access to private members) this is not a big problem. Instead ask the right questions and SFINAE on them.
Upvotes: 0
Reputation: 217810
std::is_default_constructible<PrivateConstructor>::value
has to return false
.
Giving friendship to that class doesn't guaranty to change the result (implementation might rely on other class).
There is even a proposal (p1339r0) to make it clear that we should not give friendship to std
class (unless otherwise permitted by the standard).
Upvotes: 2
Reputation: 38773
You declared the function doIt()
to be friend of the class, but that function does not access the private class members. Instead, the function std::is_deafault_constructible
accesses the class members.
template< class T >
struct is_default_constructible : std::is_constructible<T> {};
The proper way is declaring std::is_default_constructible
to be a friend class:
friend class is_default_constructible<PrivateConstructor>;
Upvotes: 1