Я прошу уловку с шаблоном, чтобы определить, есть ли у класса конкретная функция-член данной подписи.
Проблема похожа на проблему, указанную здесь http://www.gotw.ca/gotw/071.htm, но не то же самое: в пункте книги Саттера он ответил на вопрос, что класс C ДОЛЖЕН ПРЕДОСТАВЛЯТЬ функцию-член с конкретная подпись, иначе программа не скомпилируется. В моей проблеме мне нужно что-то сделать, если у класса есть эта функция, иначе сделать «что-то еще».
С аналогичной проблемой столкнулся boost :: serialization, но мне не нравится принятое ими решение: шаблонная функция, которая по умолчанию вызывает бесплатную функцию (которую вы должны определить) с определенной сигнатурой, если вы не определите конкретную функцию-член ( в их случае "сериализовать", который принимает 2 параметра заданного типа) с определенной сигнатурой, иначе произойдет ошибка компиляции. То есть для реализации как навязчивой, так и ненавязчивой сериализации.
Мне не нравится это решение по двум причинам:
- Чтобы быть ненавязчивым, вы должны переопределить глобальную функцию "сериализации", которая находится в пространстве имен boost :: serialization, поэтому у вас есть В ВАШЕМ КОДЕ КЛИЕНТА, чтобы открыть расширение пространства имен и сериализацию пространства имен!
- Стек для устранения этого беспорядка состоял из 10–12 вызовов функций.
Мне нужно определить настраиваемое поведение для классов, у которых нет этой функции-члена, и мои объекты находятся внутри разных пространств имен (и я не хочу переопределять глобальную функцию, определенную в одном пространстве имен, пока я нахожусь в другом)
Не могли бы вы мне подсказать, как решить эту головоломку?
Ответы:
Я не уверен, правильно ли я вас понял, но вы можете использовать SFINAE для обнаружения присутствия функции во время компиляции. Пример из моего кода (проверяет, есть ли у класса функция-член size_t used_memory () const).
источник
size_t(std::vector::*p)() = &std::vector::size;
.Вот возможная реализация, основанная на функциях C ++ 11. Он правильно определяет функцию, даже если она унаследована (в отличие от решения в принятом ответе, как замечает Майк Кинган в своем ответе ).
Функция, которую проверяет этот фрагмент, называется
serialize
:Использование:
источник
serialize
сам принимает шаблон. Есть ли способ проверитьserialize
существование, не вводя точный тип?Принятый ответ на этот вопрос об интроспекции функций-членов во время компиляции, хотя он и пользуется заслуженной популярностью, имеет загвоздку, которую можно наблюдать в следующей программе:
Построенный с 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 для проверки сигнатуры любого другого метода-члена.В этом решении перегруженная функция зонда SFINAE
test()
"вызывается рекурсивно". (Конечно, на самом деле он вообще не вызывается; он просто имеет типы возвращаемых гипотетических вызовов, разрешенных компилятором.)Нам нужно исследовать по крайней мере одну и максимум две точки информации:
T::operator*()
вообще? Если нет, то все готово.T::operator*()
существует, есть ли его подписьE T::operator*() const
?Мы получаем ответы, оценивая тип возвращаемого значения одного вызова
test(0,0)
. Это сделали:Этот вызов может быть разрешен к
/* 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*)
withA = 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).
Есть ли у этой идеи новые недостатки? Можно ли сделать его более общим, чтобы в очередной раз не попасть в ловушку, которую он избегает?
источник
Вот несколько примеров использования: * Все это чуть дальше
Проверить член
x
в данном классе. Может быть var, func, class, union или enum:Проверьте функцию-член
void x()
:Проверьте переменную-член
x
:Проверить класс члена
x
:Проверить членский союз
x
:Проверьте перечисление членов
x
:Проверьте любую функцию-член
x
независимо от подписи:ИЛИ
Детали и ядро:
Макросы (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
источник
Этого должно быть достаточно, если вы знаете имя ожидаемой функции-члена. (В этом случае функция bla не может создать экземпляр, если нет функции-члена (написание той, которая в любом случае работает, сложно, потому что отсутствует частичная специализация функции. Возможно, вам потребуется использовать шаблоны классов) Кроме того, структура enable (которая аналогичен enable_if) также может быть шаблонизирован для типа функции, которую вы хотите иметь в качестве члена.
источник
Вот более простой вариант ответа Майка Кингана. Это обнаружит унаследованные методы. Он также проверит точную подпись (в отличие от подхода jrok, который позволяет преобразовывать аргументы).
Запускаемый пример
источник
using
для переноса перегрузок из базового класса. У меня это работает на MSVC 2015 и с Clang-CL. Однако он не работает с MSVC 2012.Вы можете использовать std :: is_member_function_pointer
источник
&A::foo
будет ли ошибка компиляции, если ее нетfoo
вообщеA
? Я прочитал исходный вопрос как предполагаемый для работы с любым классом ввода, а не только с теми, у которых есть какой-то членfoo
.Я сам пришел с такой же проблемой и нашел предлагаемые здесь решения очень интересными ... но требовал решения, которое:
Нашел еще одну ветку, предлагающую что-то подобное, основанное на обсуждении BOOST . Вот обобщение предлагаемого решения в виде объявления двух макросов для класса признаков, следуя модели классов boost :: has_ * .
Эти макросы расширяются до класса признаков со следующим прототипом:
Итак, что из этого можно сделать?
источник
Для этого нам понадобится:
type_traits
заголовке, мы хотим вернутьtrue_type
илиfalse_type
из наших перегрузокtrue_type
перегрузку, ожидающую использования,int
иfalse_type
перегрузку, ожидающую использования Variadic Parameters: «Самый низкий приоритет преобразования многоточия в разрешении перегрузки»true_type
функции, которую мы будем использовать,declval
иdecltype
позволяющей нам обнаруживать функцию независимо от различий возвращаемых типов или перегрузок между методами.Вы можете увидеть живой пример этого здесь . Но я также объясню это ниже:
Я хочу проверить наличие функции с именем,
test
которая принимает преобразованный типint
, тогда мне нужно будет объявить эти две функции:decltype(hasTest<a>(0))::value
естьtrue
(обратите внимание, что нет необходимости создавать специальные функции для борьбы сvoid a::test()
перегрузкой,void a::test(int)
принимается)decltype(hasTest<b>(0))::value
естьtrue
(посколькуint
можно преобразовать вdouble
int b::test(double)
, независимо от типа возврата)decltype(hasTest<c>(0))::value
естьfalse
(c
не имеет названного метода,test
который принимает преобразованный тип,int
поэтому это не принимается)У этого решения есть 2 недостатка:
test()
метод?Поэтому важно, чтобы эти функции были объявлены в пространстве имен подробностей или, в идеале, если они должны использоваться только с классом, они должны быть объявлены этим классом конфиденциально. С этой целью я написал макрос, который поможет вам абстрагироваться от этой информации:
Вы можете использовать это как:
Последующий вызов
details::test_int<a>::value
илиdetails::test_void<a>::value
будет даватьtrue
илиfalse
для целей встроенного кода или метапрограммирования.источник
Чтобы не вмешиваться, вы также можете поместить
serialize
в пространство имен сериализуемого класса или класса архива, благодаря поиску Кенига . Дополнительные сведения см. В разделе « Пространства имен для переопределения бесплатных функций» . :-)Открывать любое заданное пространство имен для реализации бесплатной функции просто неправильно. (например, вы не должны открывать пространство имен
std
для реализацииswap
своих собственных типов, вместо этого следует использовать поиск Koenig.)источник
Кажется, вам нужна идиома детектора. Вышеупомянутые ответы являются вариациями этого, которые работают с C ++ 11 или C ++ 14.
В
std::experimental
библиотеке есть функции, которые, по сути, делают это. Перерабатывая пример сверху, это может быть:Если вы не можете использовать std :: experimental, простейшую версию можно сделать следующим образом:
Поскольку has_serialize_t на самом деле является либо std :: true_type, либо std :: false_type, его можно использовать через любую из распространенных идиом SFINAE:
Или с помощью отправки с разрешением перегрузки:
источник
Ладно. Вторая попытка Ничего страшного, если тебе это тоже не нравится, я ищу другие идеи.
В статье Херба Саттера говорится о чертах характера. Таким образом, у вас может быть класс черт, создание экземпляра которого по умолчанию имеет резервное поведение, и для каждого класса, в котором существует ваша функция-член, этот класс свойств специализирован для вызова функции-члена. Я считаю, что в статье Херба упоминается метод, позволяющий сделать это, чтобы не пришлось много копировать и вставлять.
Однако, как я уже сказал, возможно, вам не нужна дополнительная работа, связанная с «маркировкой» классов, которые реализуют этот член. В таком случае я ищу третье решение ....
источник
Без поддержки C ++ 11 (
decltype
) это может сработать:SSCCE
Как это, надеюсь, работает
A
,Aa
иB
являются рассматриваемыми классами,Aa
являющимися особым классом, наследующим член, который мы ищем.В и являются заменой для корреспондентских C ++ 11 классов. Также для понимания метапрограммирования шаблонов они раскрывают саму основу трюка SFINAE-sizeof.
FooFinder
true_type
false_type
Это
TypeSink
структура шаблона, которая используется позже для передачи интегрального результатаsizeof
оператора в экземпляр шаблона для формирования типа.match
Функция другой SFINAE вид шаблона , который остается без общего аналога. Следовательно, он может быть создан только в том случае, если тип его аргумента соответствует типу, для которого он был специализирован.Обе
test
функции вместе с объявлением enum в конечном итоге образуют центральный шаблон SFINAE. Есть общий, использующий многоточие, которое возвращает,false_type
и его аналог с более конкретными аргументами, чтобы иметь приоритет.Чтобы иметь возможность создать экземпляр
test
функции с аргументом шаблонаT
,match
необходимо создать экземпляр функции, так как ее тип возвращаемого значения требуется для создания экземпляраTypeSink
аргумента. Предостережение состоит в том&U::foo
, что обращение к аргументу функции, заключенному в аргумент функции, не происходит из специализации аргумента шаблона, поэтому поиск унаследованных членов все еще выполняется.источник
Если вы используете глупость facebook, их макрос готов из коробки, чтобы помочь вам:
Хотя детали реализации такие же, как и в предыдущем ответе, использовать библиотеку проще.
источник
У меня была аналогичная потребность, и я наткнулся на это ТАК. Здесь предлагается много интересных / мощных решений, хотя это немного длинно для конкретной потребности: определить, есть ли у класса функция-член с точной сигнатурой. Так что я немного прочитал / протестировал и придумал свою версию, которая могла бы заинтересовать. Он обнаруживает:
с точной подписью. Поскольку мне не нужно записывать какую-либо подпись (для этого потребовалось бы более сложное решение), этот вариант мне подходит. В основном он использовал enable_if_t .
Вывод :
источник
Основываясь на Джрок «s ответ , я бы избежать использования вложенных классов и / или функции шаблона.
Мы можем использовать вышеуказанные макросы, как показано ниже:
Предложения приветствуются.
источник