Принятый ответ на этот вопрос функции-члена времени компиляции
интроспекция, хотя она и справедливо популярна, имеет загадку, которую можно наблюдать
в следующей программе:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
Построенный с GCC 4.6.3, программа выводит 110
- сообщая нам, что
T = std::shared_ptr<int>
не не обеспечивает int & T::operator*() const
.
Если вы еще не мудры в этом вопросе, тогда посмотрите на определение
std::shared_ptr<T>
в заголовке <memory>
будет проливать свет. В этом
реализации, std::shared_ptr<T>
является производным от базового класса
от которого он наследует operator*() const
. Таким образом, шаблон создания
SFINAE<U, &U::operator*>
, который составляет «поиск» оператора для
U = std::shared_ptr<T>
не произойдет, потому что std::shared_ptr<T>
не имеет
operator*()
само по себе и шаблон создания не
"сделать наследство".
Эта загвоздка не влияет на хорошо известный подход SFINAE, использующий трюк sizeof (),
для определения просто, имеет ли T
некоторую функцию-член mf
(см., например,
этот ответ и комментарии). Но
Установление того, что T::mf
существует, часто (обычно?) недостаточно хорошо: вы можете
Также необходимо установить, что он имеет желаемую подпись. Вот где
иллюстрированная техника оценки. Указанный вариант нужной подписи
вписывается в параметр типа шаблона, который должен быть удовлетворен
&T::mf
для проверки SFINAE. Но этот шаблонный экземпляр
Техника дает неправильный ответ, когда T::mf
наследуется.
Безопасный метод SFINAE для самоанализа во время компиляции T::mf
должен избегать
использование &T::mf
в аргументе шаблона для создания экземпляра типа, для которого SFINAE
разрешение шаблона функции зависит. Вместо этого шаблон SFINAE
разрешение может зависеть только от используемых подходящих объявлений типов
в качестве типов аргументов перегруженной функции зонда SFINAE.
В качестве ответа на вопрос, который соблюдает это ограничение, я
иллюстрировать для обнаружения во время компиляции E T::operator*() const
, для
произвольно T
и E
. Тот же шаблон будет применяться mutatis mutandis
проверить наличие любой другой подписи метода члена.
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
В этом решении вызывается перегруженная функция зонда SFINAE test()
рекурсивно ". (Конечно, на самом деле это вообще не вызывается; просто
возвращаемые типы гипотетических вызовов, разрешенных компилятором.)
Нам нужно найти как минимум одну и максимум две информации:
-
T::operator*()
существует вообще? Если нет, то мы закончили.
- Учитывая, что
T::operator*()
существует, является его подписью
E T::operator*() const
Мы получаем ответы, оценивая тип возврата одного звонка
до test(0,0)
. Это сделано:
typedef decltype(test<T>(0,0)) type;
Этот вызов может быть разрешен с перегрузкой /* SFINAE operator-exists :) */
test()
, или это может привести к перегрузке /* SFINAE game over :( */
.
Это не может разрешить перегрузку /* SFINAE operator-has-correct-sig :) */
,
потому что этот ожидает только один аргумент, а мы передаем два.
Почему мы проходим два? Просто заставить резолюцию исключить
/* SFINAE operator-has-correct-sig :) */
. Второй аргумент не имеет никакого другого значения.
Этот вызов test(0,0)
разрешит /* SFINAE operator-exists :) */
просто
в случае, если первый аргумент 0 сохраняет первый тип параметра этой перегрузки,
что decltype(&A::operator*)
, с A = T
. 0 удовлетворит этот тип
на всякий случай T::operator*
существует.
Давайте предположим, что компилятор с этим согласен. Тогда это происходит с
/* SFINAE operator-exists :) */
и нужно определить тип возвращаемого значения
вызов функции, который в этом случае decltype(test(&A::operator*))
-
тип возврата еще одного вызова test()
.
На этот раз мы передаем только один аргумент, &A::operator*
, который мы сейчас
знать существует, иначе нас бы здесь не было Звонок на test(&A::operator*)
может
разрешите либо /* SFINAE operator-has-correct-sig :) */
, либо снова
может разрешить до /* SFINAE game over :( */
. Вызов будет соответствовать
/* SFINAE operator-has-correct-sig :) */
на всякий случай &A::operator*
удовлетворяет
единственный тип параметра этой перегрузки, который E (A::*)() const
,
с A = T
.
Компилятор скажет "Да", если T::operator*
имеет желаемую подпись,
и затем снова должен оценить тип возвращаемого значения перегрузки. Больше не надо
"рекурсии" сейчас: это std::true_type
.
Если компилятор не выбирает /* SFINAE operator-exists :) */
для
звоните test(0,0)
или не выбираете /* SFINAE operator-has-correct-sig :) */
для звонка test(&A::operator*)
, то в любом случае он идет с
/* SFINAE game over :( */
и окончательный тип возврата - std::false_type
.
Вот тестовая программа, которая показывает шаблон, выдающий ожидаемый
ответы в различных случаях (снова GCC 4.6.3).
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
Есть ли новые недостатки в этой идее? Можно ли сделать это более общим без повторения
не справиться с этой загадкой?