我很惊讶地发现std::is_default_constructible
似乎忽略了朋友访问权限。在一个类中声明一个私有的默认构造函数然后为一个函数提供信息时,我希望std::is_default_constructible
将返回true。
示例:我使用Clang 5.0.0和C ++ 17下的GCC 7.2.0在Wandbox上运行了以下内容:https://wandbox.org/。
#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;
}
此代码编译但断言失败。是在标准中明确定义的还是可能的编译器错误?
答案 0 :(得分:1)
您声明函数doIt()
是该类的朋友,但该函数不访问私有类成员。相反,函数std::is_deafault_constructible
访问类成员。
template< class T >
struct is_default_constructible : std::is_constructible<T> {};
正确的方法是宣布std::is_default_constructible
为朋友类:
friend class is_default_constructible<PrivateConstructor>;
答案 1 :(得分:1)
std::is_default_constructible<PrivateConstructor>::value
必须返回false
。
向该班级授予友谊并不能保证改变结果(实现可能取决于其他班级)。
甚至有一项提案(p1339r0)明确指出,我们不应该为std
类提供友谊(除非标准另行许可)。
答案 2 :(得分:0)
我认为有几点要总结:
GameSalute已通过Clang和GCC进行了测试。为了进一步向您展示这可能导致的结果,我已经在C ++ 14模式下使用Visual Studio 2017 V15.9.11(当前是最新版本)对MSVC进行了一些实验:
#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");
}
};
您可以从此代码段中看到什么:
因此,我所建议的就是实际上不要在命名空间std中进行交朋友。基于这种构造的所有内容都是不可移植的,并且在当今的编译器中也部分被破坏和不一致(至少对于MS而言)。无论如何,因为请求私有默认构造函数是很荒谬的(除非您正在实施需要对私有成员进行非侵入式访问的超级智能Boost序列化程序类),这不是一个大问题。而是问正确的问题,并向SFINAE提问。