具有私有访问权限和友元函数的std :: is_default_constructible

时间:2018-03-14 02:44:07

标签: c++ c++11

我很惊讶地发现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;
}

此代码编译但断言失败。是在标准中明确定义的还是可能的编译器错误?

3 个答案:

答案 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)

我认为有几点要总结:

  • S.M。的评论这表明该标准似乎正在将std :: is_default_constructible绑定到某个类的 public 接口。这意味着std :: is_default_constructible将为具有私有或受保护的默认构造函数的类产生 false 。这是有原因的。
  • 有几个答案建议与std :: is_default_constructible成为朋友,以获得所需的 true 值。这是错误的,应该避免,解释如下。
  • 通常,询问是否存在非公共默认构造函数会提出错误的问题,甚至是无意义的问题!考虑:
    • 您可以合理地要求实现 global 的可构造性(通过std :: is_default_constructible),从而向您提供任何人是否可以构建特定对象的信息(由于公开了公共构造函数) )。
    • 但是,如果询问class是否具有私有构造函数,它将为您提供什么信息?并不是每个人都可以构造这样的对象吗?它对您有什么价值,您对此会有何看法?您没有专门的信息来指定谁可以和谁不能构建此对象,因此了解私有构造函数不会回答任何问题。
    • 您应该问的问题是您专门针对实例化该特定对象的类是否可以构造该对象,对吗?因此,您需要在好友类/函数内部而不是全局进行SFINAE。
  • Jarod42的评论提供了此链接:Disallowing the friending of names in namespace std。为什么是这样?因为在某些功能的实现中要做的事情绝对是实现定义的。因此,使std :: is_default_constructible成为某个类的朋友可能会在编译器A上产生预期的结果,但在B上将失败。或者在A的下一个版本中可能会失败。您不想依赖它吗? / li>

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::is_default_constructible_v),但是MS使用某种内在的编译器来实现std :: is_default_constructible和std :: is_default_constructible_v:__is_constructible(Type)。这导致下面提到的偏离行为。
  • MS 执行评估类的友谊,因此给出的结果与Clang和GCC不同。
  • 在MS当前的化身中,std :: is_default_constructible :: value与std :: is_default_constructible_v给出不同的结果(!!)。如果您天真地对测试结果进行SFINAE,这将是致命的!
  • 在注释掉的代码中,我尝试了一些(当然是荒谬的)与std :: is_default_constructible_v友好的方式。某些可能会失败(C2433,C2063,C2059,C2238),但是甚至会导致内部编译器错误(C1903)!

因此,我所建议的就是实际上不要在命名空间std中进行交朋友。基于这种构造的所有内容都是不可移植的,并且在当今的编译器中也部分被破坏和不一致(至少对于MS而言)。无论如何,因为请求私有默认构造函数是很荒谬的(除非您正在实施需要对私有成员进行非侵入式访问的超级智能Boost序列化程序类),这不是一个大问题。而是问正确的问题,并向SFINAE提问。