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_if
s.
详情
我冒昧地将 #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(及更早版本)似乎尝试实例化模板并根据上下文进行访问检查。因此,如果第一个实例化是在友元之外,则 .test()
方法不可访问,并且结果会被缓存。但是,如果它在 friend void foo()
内,则可以访问 .test()
并且所有 static_assert
都成功。
@Klaus 指出了最近的 GCC 错误,其修复似乎相关:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96204
我有一个 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_if
s.
详情
我冒昧地将 #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(及更早版本)似乎尝试实例化模板并根据上下文进行访问检查。因此,如果第一个实例化是在友元之外,则 .test()
方法不可访问,并且结果会被缓存。但是,如果它在 friend void foo()
内,则可以访问 .test()
并且所有 static_assert
都成功。
@Klaus 指出了最近的 GCC 错误,其修复似乎相关:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96204