我有一个由未指定类继承的 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
}
请注意,我使用 _T
在decltype
里面所以它依赖于模板参数并且可以是 SFINAEd。另请注意,仍然可以在没有任何 enable_if
的情况下指定任意返回类型。
详情
我冒昧地在前面添加了#include <type_traits>
和 using namespace std;
到您的两个示例并使用 C++17,以便它们可以被编译。
评论区的一些发现:
- 您的第一个代码(未)按预期使用 Clang 14、gcc 11 和 gcc trunk 编译:https://godbolt.org/z/EbaYvfPE3
- 您的第二个代码(未)按预期使用 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/