C++通过CRTP检测朋友课的私人成员

发布于 2025-01-19 22:24:12 字数 3187 浏览 2 评论 0原文

我有一个 CRTP 基类 (Bar),它由未指定的类继承。此派生类可能有也可能没有特定成员 (internal_foo),并且该特定成员可能有也可能没有其他成员 (test())。

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

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

下面的示例由于 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:

    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 & 合取)

编辑:

建议的问题没有回答这个问题。这个问题想要检测一个成员是公共的还是私有的,并且只有在它是公共的情况下才访问它,我希望检测对朋友类的私有成员返回肯定

I have a CRTP Base class (Bar) which is inherited by a unspecified class. This Derived class may or may not have specific member (internal_foo), and this specific member my or may not have another member (test()).

In this scenario internal_foo will always be public, however test() is private, but declaring Bar as a friend.

I can detect internal_foo using traits fine, because it is public. But I cannot detect test() due to it being private, even though Bar is a friend.

The below example works due to test() being 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.
}

However this next version does not work, due to test() being private:

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
}

As seen above, I have tried to friend the detection struct too, but that didn't help.

Is there a way to do what I am trying to do?

Ideally I would like this solution to be portable, and not use anything beyond C++11, 14 at the most. (I have implemented void_t & conjunction)

Edit:

The suggested question does not answer this one. That question wants to detect whether a member is public or private, and only access it if it is public, I wish for the detection to return positive on a private member of a friend class.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

云仙小弟 2025-01-26 22:24:12

摘要和修复

看起来像 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
}

请注意,我在 中使用 _T decltype 因此它依赖于模板参数并且可以被 SFINAEd 处理。另请注意,仍然可以指定任意返回类型而不使用任何 enable_if

详细信息

我冒昧地在您的两个示例中添加了 #includeusing 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 拒绝,并显示 3 条“静态断言失败”消息,每个断言各一条。但是,注释掉第一个 static_assert 会使所有三个编译器都接受该代码。

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

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

Summary and the fix

Looks like a GCC 11 bug and your second attempt should in fact work.

However, I recommend rewriting action's definition in either of two ways so you don't even need the member detection idiom:

// 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
}

Note that I use _T inside the decltype so it's dependent on the template argument and can be SFINAEd. Also note that it's still possible to specify an arbitrary return return type without any enable_ifs.

Details

I took the liberty to prepend #include <type_traits> and using namespace std; to both of your examples and using C++17 so they can be compiled.

Some findings from the comments section:

  1. Your first code (does not) compile(s) as expected with Clang 14, gcc 11 and gcc trunk: https://godbolt.org/z/EbaYvfPE3
  2. Your second code (does not) compile(s) as expected with Clang add gcc trunk, but gcc 11 differs: https://godbolt.org/z/bbKrP8Mb9

There is an easier reproduction example: 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, "");

The code above compiles with Clang 14 and gcc trunk, but is rejected by gcc 11 with three "static assertion failed" messages, one for each assertion. However, commenting out the first static_assert makes all three compilers accept the code.

So it seems like GCC 11 (and earlier) tries to instantiate the template and do access checks depending on the context. Hence, if the first instantiation is outside of a friend, .test() method is not accessible, and the result is kind cached. However, if it's inside the friend void foo(), .test() is accessible and all static_asserts succeed.

@Klaus have pointed out a recent GCC bug whose fix seems relevant: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96204

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文