我有一个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_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的预期编译,但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