У меня есть базовый класс CRTP (Бар), который наследуется неопределенным классом. Этот производный класс может иметь или не иметь определенного члена (internal_foo), и этот конкретный член может иметь или не иметь другого члена (контрольная работа()).
В этом сценарии internal_foo всегда будет общедоступным, однако контрольная работа() является частным, но объявляет Бар как друга.
Я могу определить internal_foo с помощью признаков, потому что он общедоступен. Но я не могу обнаружить контрольная работа() из-за того, что он частный, хотя Бар — друг.
Пример ниже работает из-за того, что контрольная работа() является общедоступным:
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.
}
Однако эта следующая версия не работает из-за того, что контрольная работа() является закрытым:
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
}
Как видно выше, я также пытался подружить структуру обнаружения, но это не помогло.
Есть ли способ сделать то, что я пытаюсь сделать?
В идеале я хотел бы, чтобы это решение было переносимым и не использовало ничего, кроме С++ 11, максимум 14. (Я реализовал void_t и соединение)
Редактировать:
Предлагаемый вопрос не отвечает на этот вопрос. Этот вопрос хочет определить, является ли член общедоступным или частным, и получить к нему доступ только в том случае, если он является общедоступным, я хочу, чтобы обнаружение возвращало положительный для частного члена класса друзей.
Отвечает ли это на ваш вопрос? Как проверить доступ к приватной функции?
Вы случайно не используете GCC? Ваш код работает с Clang?
@yeputons: Спасибо! Я даже использовал эту технику раньше, но я не знаю, почему она не пришла ко мне в этот раз. Я использую Visual Studio 2019 прямо сейчас, но я использую GCC в Linux с той же базой кода, отсюда и переносимость.
Кажется, что частные участники и друзья играть странно с SFINAE.
Также была ошибка в gcc: gcc.gnu.org/bugzilla/show_bug.cgi?id=96204. Это исправлено в gcc 12 (надеюсь :-)).
Ах, да, эта проблема с SFINAE под Расхождение по другу с шаблонами - это именно то, с чем я столкнулся.
Вот более короткий пример разного поведения GCC и Clang: godbolt.org/z/T17dG3Mx1. Более того, закомментирование одного из static_assert
заставляет GCC принять код. Странные вещи.
@yeputons: Это также исправлено в gcc 12... возможно, это связано с моей предыдущей командой (отчет об ошибке для gcc). Просто используйте gcc 12 на godbolt, теперь все работает!
@yeputons, это определенно странное поведение.
Похоже на ошибку 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
s.
Я позволил себе добавить #include <type_traits>
и using namespace std;
к обоим вашим примерам и использовать C++17, чтобы их можно было скомпилировать.
Некоторые выводы из раздела комментариев:
Есть более простой пример воспроизведения: 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, но 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
Более или менее с тем же материалом SFINAE у меня не получилось, что послужило причиной заполнения отчета об ошибке. Но я переключился на концепции для таких случаев использования ... мне кажется, их намного легче читать.
Если вам просто нужна условная компиляция на
action
, избавьтесь от обнаружения членов и сделайте так, чтобыaction
возвращалdecltype(static_cast<T&>(*this).internal_foo.test())
вместоenable_if_t
s