C++ 使用 CRTP 检测朋友 class 的私有成员

C++ Detect private member of friend class with CRTP

我有一个 CRTP Base class (Bar),它被未指定的 class 继承。此 Derived class 可能有也可能没有特定成员 (internal_foo),并且此特定成员可能有或可能没有其他成员 (test ()).

在这种情况下 internal_foo 将始终是 public,但是 test() 是私有的,但是声明 Bar 为好友。

我可以很好地检测到 internal_foo,因为它是 public。但是我无法检测到 test() 因为它是私有的,即使 Bar 是朋友。

由于 test() 为 public:

,因此下面的示例有效
template<class, class = void >
struct has_internal_foo : std::false_type {};

template<class T>
struct has_internal_foo<T,
    void_t<
    decltype(std::declval<T>().internal_foo)
    >> : std::true_type {};

template<class, class = void>
struct internal_foo_has_test : std::false_type {};

template<class T>
struct internal_foo_has_test<T,
    void_t<decltype(std::declval<T>().internal_foo.test())
    >> : std::true_type {};

class InternalFoo
{
public:

    void test()
    {

    }
};

class BadInternalFoo
{
};

template<class T>
class Bar
{
public:
    template<class _T = T>
    std::enable_if_t<conjunction<has_internal_foo<_T>, internal_foo_has_test<_T>>::value, void>
        action()
    {
        static_cast<T&>(*this).internal_foo.test();
    }
};

class Foo : 
    public Bar<Foo>
{
public:
    InternalFoo internal_foo;
};

class BadFoo :
    public Bar<BadFoo>
{
public:
    BadInternalFoo internal_foo;
};

void test()
{
    Foo foo;
    BadFoo bad_foo;

    foo.action(); // Compiles. As expected.
    bad_foo.action(); // Does not compile. As expected.
}

然而,由于 test() 是私有的,因此下一个版本不起作用:

template<class, class = void >
struct has_internal_foo : std::false_type {};

template<class T>
struct has_internal_foo<T,
    void_t<
    decltype(std::declval<T>().internal_foo)
    >> : std::true_type {};

template<class, class = void>
struct internal_foo_has_test : std::false_type {};

template<class T>
struct internal_foo_has_test<T,
    void_t<decltype(std::declval<T>().internal_foo.test())
    >> : std::true_type {};

class InternalFoo
{
public:
    template<class T>
    friend class Bar;

    template<class, class>
    friend struct internal_foo_has_test;

private:
    void test()
    {

    }
};

class BadInternalFoo
{
};

template<class T>
class Bar
{
public:
    template<class _T = T>
    std::enable_if_t<conjunction<has_internal_foo<_T>, internal_foo_has_test<_T>>::value, void>
        action()
    {
        static_cast<T&>(*this).internal_foo.test();
    }
};

class Foo : 
    public Bar<Foo>
{
public:
    InternalFoo internal_foo;
};

class BadFoo :
    public Bar<BadFoo>
{
public:
    BadInternalFoo internal_foo;
};

void test()
{
    Foo foo;
    BadFoo bad_foo;

    foo.action(); // Does not compile
    bad_foo.action(); // Does not compile
}

如上所示,我也尝试过将检测结构添加为好友,但这没有帮助。

有没有办法做我想做的事?

理想情况下,我希望这个解决方案是可移植的,并且最多不使用 C++11、14 以外的任何东西。 (我实现了 void_t & conjunction)

编辑:

建议的问题没有回答这个问题。那个问题想检测一个成员是public还是private,如果是public才访问它,我希望检测到returnpositive 在朋友的私人会员上 class.

总结和修正

看起来像是 GCC 11 错误,您的第二次尝试应该确实有效。

但是,我建议以两种方式之一重写 action 的定义,这样您甚至不需要成员检测习惯用法:

// Way 1
template<class _T = T>
decltype(std::declval<_T&>().internal_foo.test()) action() {
    static_cast<T&>(*this).internal_foo.test();
}

// Way 1, with a different return type via the comma operator
template<class _T = T>
decltype(std::declval<_T&>().internal_foo.test(), std::declval<ReturnType>()) action() {
    static_cast<T&>(*this).internal_foo.test();
}

// Way 2
template<class _T = T>
auto action() -> decltype(static_cast<_T&>(*this).internal_foo.test()) {
    static_cast<_T&>(*this).internal_foo.test();  // Using _T for consistency
}

请注意,我在 decltype 中使用了 _T,因此它依赖于模板参数并且可以被 SFINAEd。另请注意,仍然可以指定任意 return return 类型而无需任何 enable_ifs.

详情

我冒昧地将 #include <type_traits>using namespace std; 添加到您的两个示例中,并使用 C++17 进行编译。

评论区的一些发现:

  1. 您的第一个代码(未)按预期使用 Clang 14、gcc 11 和 gcc trunk 编译:https://godbolt.org/z/EbaYvfPE3
  2. 你的第二个代码(没有)按预期使用 Clang add gcc trunk 编译,但 gcc 11 不同:https://godbolt.org/z/bbKrP8Mb9

有一个更容易重现的例子:https://godbolt.org/z/T17dG3Mx1

#include <type_traits>

template<class, class = void>
struct has_test : std::false_type {};

template<class T>
struct has_test<T, std::void_t<decltype(std::declval<T>().test())>> : std::true_type {};

class HasPrivateTest
{
public:
    template<class, class>
    friend struct has_test;
    friend void foo();

private:
    void test() {}
};

// Comment the following line to make it compile with GCC 11
static_assert(has_test<HasPrivateTest>::value, "");
void foo() {
    static_assert(has_test<HasPrivateTest>::value, "");
}
static_assert(has_test<HasPrivateTest>::value, "");

上面的代码使用 Clang 14 和 gcc trunk 编译,但被 gcc 11 拒绝并显示三个“静态断言失败”消息,每个断言一个。但是,注释掉第一个 static_assert 会使所有三个编译器都接受该代码。

所以 GCC 11(及更早版本)似乎尝试实例化模板并根据上下文进行访问检查。因此,如果第一个实例化是在友元之外,则 .test() 方法不可访问,并且结果会被缓存。但是,如果它在 friend void foo() 内,则可以访问 .test() 并且所有 static_assert 都成功。

@Klaus 指出了最近的 GCC 错误,其修复似乎相关:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96204