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



我有一个CRTP基类(Bar(,它由一个未指定的类继承。这个派生类可能有也可能没有特定的成员(internal_fo(,而这个特定的成员可能有或没有另一个成员(test(((。

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

我可以很好地使用traits检测internal_fo,因为它是公共的。但我无法检测到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&conjunction(

编辑:

建议的问题不能回答这个问题。这个问题想要检测一个成员是公共成员还是私有成员,并且只有在它是公共成员时才访问它,我希望检测在朋友类的私有成员上返回

摘要和修复

看起来像是一个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。还要注意,在没有任何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的预期编译,但gcc11不同: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

最新更新