【问题标题】:std::is_default_constructible with private access and friend function具有私有访问和好友功能的 std::is_default_constructible
【发布时间】:2018-03-14 02:44:07
【问题描述】:

我惊讶地发现std::is_default_constructible 似乎忽略了好友访问。当在一个类中声明一个默认构造函数私有然后给一个函数加好友时,我希望 std::is_default_constructible 会返回 true。

示例:我在 Wandbox 上运行以下命令:https://wandbox.org/ 在 C++17 下使用 Clang 5.0.0 和 GCC 7.2.0。

#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;
}

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

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    std::is_default_constructible&lt;PrivateConstructor&gt;::value 必须返回 false

    为该类提供友谊并不能保证改变结果(实现可能依赖于其他类)。

    甚至有一个提议 (p1339r0) 明确表示我们不应该给 std 类提供友谊(除非标准另有允许)。

    【讨论】:

      【解决方案2】:

      您将函数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>;
      

      【讨论】:

      • 这是有道理的,但为什么不只是friend struct std::is_default_constructible?我尝试了两种变体,但断言仍然失败。
      • 哦,我发现了一个 GCC 的错误报告 G++ 在模板推导期间还没有实现访问检查gcc.gnu.org/bugzilla/show_bug.cgi?id=53734
      • 我还从 Google ISO C++ 标准论坛中找到了引用:§20.10.4.3/7 说 is_constructible 仅反映公共接口
      • 我看到上面的参考资料还指出“访问检查是在与 T 和任何 Args 无关的上下文中执行的”,表明友元声明将被忽略。我们可以在en.cppreference.com/w/cpp/types/is_constructible 找到这个提炼的内容。
      • 由于 GameSalutes 提出的观点,这个答案从来都不是真正正确的。访问检查的上下文与任何东西都不相关,所以你不能friend 它。第二句是关于_v 变量的,它说它们的访问检查是在变量初始化的上下文中(即:全局),而不是变量usage的点。
      【解决方案3】:

      我觉得有几点可以总结一下:

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

      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");
          }
      };
      

      你可以从这个 sn-p 看到什么:

      • 该标准建议了一个特定的实现 (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)。无论如何,因为要求一个私有的默认构造函数是相当荒谬的(除非你正在实现一个需要非侵入式访问私有成员的超级智能提升序列化程序类),这不是一个大问题。而是提出正确的问题和 SFINAE。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-08-21
        • 1970-01-01
        • 1970-01-01
        • 2021-08-05
        • 2021-11-15
        相关资源
        最近更新 更多