C++ 使用 CRTP 检测 friend 类的私有(private)成员

标签 c++ sfinae typetraits crtp enable-if

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

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

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

由于 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() 是私有(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
}

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

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

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

编辑:

建议的问题没有回答这个问题。该问题想要检测一个成员是公共(public)成员还是私有(private)成员,并且只有在它是公共(public)成员时才访问它,我希望检测在 friend 类的私有(private)成员上返回正面

最佳答案

总结和修复

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

请注意,我使用 _Tdecltype里面所以它依赖于模板参数并且可以是 SFINAEd。另请注意,仍然可以在没有任何 enable_if 的情况下指定任意返回类型。

详情

我冒昧地在前面添加了#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(及更早版本)尝试实例化模板并根据上下文进行访问检查。因此,如果第一个实例化是在 friend 之外,.test()方法不可访问,结果被缓存。但是,如果它在 friend void foo() 内, .test()可以访问并且所有static_assert我们成功了。

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

关于C++ 使用 CRTP 检测 friend 类的私有(private)成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71795886/

相关文章:

c++ - 静态重载与 SFINAE 结合使用

c++ - 在派生模板类中使用条件类型特征覆盖基类中的虚拟方法

c++ - 对于类型修改特征,我应该为 typename::transform<...>::type 提供一个 template-typedef(又名 using)便利包装器吗?

c++ - boost::optional<size_t>& = boost::none 编译错误

c++ - clang 格式的多行数组定义和结束大括号

c++ - 如果加载到应用程序,DLL 中的函数地址是否可以更改?

c++调用c函数,如果它确实存在

c++ - 使用 clang++ 和 g++ 的 SFINAE 和 CRTP 的一些魔法

c++ - 按位运算会帮助我序列化一些 bool 值吗?

c++ - 是否有类型特征可以立即删除顶级 cv 和引用?