Можно ли написать шаблон, который меняет поведение в зависимости от того, определена ли определенная функция-член в классе?
Вот простой пример того, что я хотел бы написать:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
Таким образом, если class T
уже toString()
определен, то он использует его; в противном случае это не так. Волшебная часть, которую я не знаю, как сделать, это часть "FUNCTION_EXISTS".
Ответы:
Да, с SFINAE вы можете проверить, предоставляет ли данный класс определенный метод. Вот рабочий код:
Я только что проверил это с Linux и GCC 4.1 / 4.3. Я не знаю, переносимо ли это на другие платформы, на которых работают другие компиляторы.
источник
typeof
наdecltype
при использовании C ++ 0x , например, с помощью -std = C ++ 0x.Этот вопрос старый, но с C ++ 11 мы получили новый способ проверки существования функций (или существования любого не типового члена, на самом деле), снова полагаясь на SFINAE:
Теперь о некоторых объяснениях. Во-первых, я использую выражение SFINAE чтобы исключить
serialize(_imp)
функции из разрешения перегрузки, если первое выражение внутриdecltype
недопустимо (иначе функция не существует).void()
Используется , чтобы сделать тип возвращаемого значения всех этих функцийvoid
.0
Аргумент используется предпочитатьos << obj
перегрузки , если оба доступны (дословный0
имеет типаint
и как таковую первую перегрузка лучше подходит).Теперь вы, вероятно, хотите, чтобы признак проверял, существует ли функция. К счастью, это легко написать. Заметим, однако, что вам нужно написать черта себя для каждого различного имени функции вы можете захотеть.
Живой пример.
И по объяснениям. Во-первых,
sfinae_true
это вспомогательный тип, и он в основном равен написаниюdecltype(void(std::declval<T>().stream(a0)), std::true_type{})
. Преимущество просто в том, что он короче.Далее,
struct has_stream : decltype(...)
наследуется от одногоstd::true_type
илиstd::false_type
в конце, в зависимости от того, не прошла лиdecltype
регистрацияtest_stream
.Наконец,
std::declval
дает вам «значение» любого типа, который вы передаете, без необходимости знать, как вы можете его построить. Обратите внимание, что это возможно только в недооцененном контексте, напримерdecltype
,sizeof
и другие.Обратите внимание, что
decltype
это не обязательно необходимо, посколькуsizeof
(и все неоцененные контексты) получили это улучшение. Просто этоdecltype
уже дает тип и, как таковое, просто чище. Вотsizeof
версия одной из перегрузок:Параметры
int
иlong
по-прежнему присутствуют по той же причине. Указатель массива используется для предоставления контекста, в которомsizeof
можно использовать.источник
decltype
oversizeof
также заключается в том, что временное не вводится специально созданными правилами для вызовов функций (поэтому вам не нужно иметь права доступа к деструктору возвращаемого типа и не будет вызываться неявная реализация, если возвращаемый тип создание шаблона класса).static_assert(has_stream<X, char>() == true, "fail X");
будет компилироваться, а не утверждаться, потому что char может быть преобразован в int, поэтому, если такое поведение нежелательно и нужно, чтобы все типы аргументов совпадали, я не знаю, как этого можно достичь?C ++ позволяет использовать SFINAE для этого (обратите внимание, что с функциями C ++ 11 это проще, потому что он поддерживает расширенный SFINAE для почти произвольных выражений - ниже было создано для работы с обычными компиляторами C ++ 03):
вышеупомянутый шаблон и макрос пытается создать экземпляр шаблона, давая ему тип указателя на функцию-член и фактический указатель на функцию-член. Если типы не подходят, SFINAE приводит к игнорированию шаблона. Использование как это:
Но обратите внимание, что вы не можете просто вызвать эту
toString
функцию в этой ветке if. поскольку компилятор проверит правильность в обеих ветвях, это может привести к сбою в случаях, когда функция не существует. Один из способов - использовать SFINAE еще раз (enable_if также можно получить из boost):Получайте удовольствие, используя его. Преимущество этого в том, что он также работает для перегруженных функций-членов, а также для константных функций-членов (помните,
std::string(T::*)() const
что в качестве типа указателя на функцию-член используйте!).источник
type_check
используется, чтобы убедиться, что подписи точно совпадают. Есть ли способ сделать так, чтобы он соответствовал любому методу, который мог бы быть вызван так, какSign
мог бы быть вызван метод с сигнатурой ? (Например, еслиSign
=std::string(T::*)()
, разрешитеstd::string T::toString(int default = 42, ...)
совпадение.)T
не должен быть примитивным типом, поскольку объявление указателя на метод T не относится к SFINAE и приведет к ошибке для любого класса, не относящегося к T. ИМО самое простое решение - объединить его сis_class
проверкой из увеличение.toString
функция является шаблонной?С ++ 20 -
requires
выраженияС C ++ 20 приходят концепции и различные инструменты, такие как
requires
выражения, которые являются встроенным способом проверки существования функции. С их помощью вы можете переписать своюoptionalToString
функцию следующим образом:Pre-C ++ 20 - Набор инструментов для обнаружения
N4502 предлагает инструментарий обнаружения для включения в стандартную библиотеку C ++ 17, которая в итоге вошла в основы библиотеки TS v2. Скорее всего, он никогда не попадет в стандарт, потому что с тех пор он был включен в
requires
выражения, но он все же решает проблему несколько элегантным образом. Инструментарий представляет некоторые метафункции, в том числе,std::is_detected
которые можно легко использовать для написания метафункций обнаружения типов или функций поверх них. Вот как вы можете использовать это:Обратите внимание, что приведенный выше пример не проверен. Набор инструментов для обнаружения еще не доступен в стандартных библиотеках, но в предложении содержится полная реализация, которую вы можете легко скопировать, если вам это действительно нужно. Это хорошо работает с функцией C ++ 17
if constexpr
:C ++ 14 - Boost.Hana
Boost.Hana очевидно основывается на этом конкретном примере и предоставляет решение для C ++ 14 в своей документации, поэтому я собираюсь процитировать его непосредственно:
Boost.TTI
Еще один идиоматический инструментарий для выполнения такой проверки - хотя и менее элегантный - это Boost.TTI , представленный в Boost 1.54.0. Для вашего примера вам придется использовать макрос
BOOST_TTI_HAS_MEMBER_FUNCTION
. Вот как вы можете использовать это:Затем вы можете использовать
bool
для создания проверки SFINAE.объяснение
Макрос
BOOST_TTI_HAS_MEMBER_FUNCTION
генерирует метафункцию,has_member_function_toString
которая принимает проверенный тип в качестве первого параметра шаблона. Второй параметр шаблона соответствует типу возвращаемого значения функции-члена, а следующие параметры соответствуют типам параметров функции. Членvalue
содержит,true
если классT
имеет функцию-членstd::string toString()
.В качестве альтернативы
has_member_function_toString
может принимать указатель на функцию-член в качестве параметра шаблона. Следовательно, его можно заменитьhas_member_function_toString<T, std::string>::value
наhas_member_function_toString<std::string T::* ()>::value
.источник
Хотя этому вопросу два года, я позволю себе добавить свой ответ. Надеемся , что она будет разъяснено предыдущий, бесспорно отличный, решение. Я взял очень полезные ответы Никола Бонелли и Йоханнеса Шауба и объединил их в решение, которое, ИМХО, более читабельно, понятно и не требует
typeof
расширения:Я проверил это с помощью gcc 4.1.2. Кредит в основном принадлежит Никола Бонелли и Йоханнесу Шаубу, так что дайте им право голоса, если мой ответ поможет вам :)
источник
toString
. Если вы пишете универсальную библиотеку, которая хочет работать с любым классом (подумайте о чем-то вроде boost), то требование пользователя определить дополнительные специализации некоторых непонятных шаблонов может быть неприемлемым. Иногда желательно написать очень сложный код, чтобы сделать публичный интерфейс настолько простым, насколько это возможно.Простое решение для C ++ 11:
Обновление, 3 года спустя: (и это не проверено). Чтобы проверить на существование, я думаю, что это будет работать:
источник
template<typename>
вариадической перегрузки: это не рассматривалось для разрешения.Вот для чего нужны черты типа. К сожалению, они должны быть определены вручную. В вашем случае представьте следующее:
источник
&T::x
или неявно, связывая его со ссылкой).type traits
для условной компиляции в C ++ 11Ну, на этот вопрос уже есть длинный список ответов, но я хотел бы подчеркнуть комментарий Morwenn: есть предложение для C ++ 17, которое делает его действительно намного проще. Смотрите N4502 , но в качестве отдельного примера рассмотрите следующее.
Эта часть является постоянной частью, поместите ее в заголовок.
затем есть переменная часть, где вы указываете, что вы ищете (тип, тип члена, функцию, функцию-член и т. д.). В случае ОП:
Следующий пример, взятый из N4502 , показывает более сложный зонд:
По сравнению с другими реализациями, описанными выше, эта довольно проста: достаточно сокращенного набора инструментов (
void_t
иdetect
), нет необходимости в волосатых макросах. Кроме того, сообщалось (см. N4502 ), что он заметно более эффективен (время компиляции и потребление памяти компилятором), чем предыдущие подходы.Вот живой пример . Он отлично работает с Clang, но, к сожалению, версии GCC до 5.1 следовали другой интерпретации стандарта C ++ 11, которая
void_t
не работала должным образом. Yakk уже предоставил обходной путь: используйте следующее определениеvoid_t
( void_t в списке параметров работает, но не как тип возвращаемого значения ):источник
Это решение C ++ 11 для общей проблемы, если «Если бы я сделал X, он бы скомпилировал?»
has_to_string
Такая чертаhas_to_string<T>::value
есть,true
если и только еслиT
есть метод,.toString
который может быть вызван с 0 аргументами в этом контексте.Далее я бы использовал диспетчеризацию тегов:
который имеет тенденцию быть более понятным, чем сложные выражения SFINAE.
Вы можете написать эти черты с помощью макроса, если вы обнаружите, что делаете это много, но они относительно просты (несколько строк каждая), поэтому, может быть, это того не стоит:
вышеизложенное создает макрос
MAKE_CODE_TRAIT
. Вы передаете ему имя желаемой черты и некоторый код, который может проверить типT
. Таким образом:создает вышеупомянутый класс черт.
Кроме того, вышеупомянутая методика является частью того, что MS называет «выражением SFINAE», и их компилятор 2013 года терпит неудачу довольно сильно.
Обратите внимание, что в C ++ 1y возможен следующий синтаксис:
которая является условной веткой встроенной компиляции, которая использует множество функций C ++. Делать это, вероятно, не стоит, так как выгода (от встроенного кода) не стоит затрат (почти никто не понимает, как это работает), но может оказаться интересным наличие вышеупомянутого решения.
источник
has_to_string
.Вот некоторые фрагменты использования: * Внутренности для всего этого находятся ниже
Проверьте для члена
x
в данном классе. Может быть var, func, class, union или enum:Проверьте функцию члена
void x()
:Проверьте переменную члена
x
:Проверьте для класса участника
x
:Проверить членство в профсоюзе
x
:Проверьте перечисление членов
x
:Проверьте любую функцию-член
x
независимо от подписи:ИЛИ
Детали и ядро:
Макросы (Эль Диабло!):
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:
источник
sig_check<func_sig, &T::func_name>
к бесплатной проверке функций:sig_check<func_sig, &func_name>
она не будет собрана с «необъявленным идентификатором» с упоминанием имени функции, которую мы хотим проверить? потому что я ожидал бы, что SFINAE сделает это НЕ ошибкой, он делает это только для членов, почему не для свободных функций?Я написал ответ на это в другой ветке, которая (в отличие от решений выше) также проверяет унаследованные функции-члены:
SFINAE для проверки унаследованных функций-членов
Вот несколько примеров из этого решения:
Example1:
Мы проверяем участника со следующей подписью:
T::const_iterator begin() const
Обратите внимание, что он даже проверяет константность метода, а также работает с примитивными типами. (Я имею в виду
has_const_begin<int>::value
false и не вызывает ошибку во время компиляции.)Пример 2
Теперь мы ищем подпись:
void foo(MyClass&, unsigned)
Обратите внимание, что MyClass не должен быть конструируемым по умолчанию или соответствовать какой-либо специальной концепции. Техника работает и с членами шаблона.
Я с нетерпением жду мнения по этому поводу.
источник
Теперь это было приятно маленькая загадка - отличный вопрос!
Вот альтернатива решению Никола Бонелли, которое не опирается на нестандартный
typeof
оператор.К сожалению, он не работает на GCC (MinGW) 3.4.5 или Digital Mars 8.42n, но работает на всех версиях MSVC (включая VC6) и на Comeau C ++.
Более длинный блок комментариев содержит подробную информацию о том, как он работает (или должен работать). Как говорится, я не уверен, какое поведение соответствует стандартам - я хотел бы получить комментарии по этому поводу.
обновление - 7 ноября 2008 г .:
Похоже, в то время как этот код синтаксически корректен, поведение, которое демонстрируют MSVC и Comeau C ++, не соответствует стандарту (спасибо Леону Тиммермансу и пользователю litb за то, что он указал мне правильное направление). Стандарт C ++ 03 гласит следующее:
Итак, это выглядит так, когда MSVC или Comeau рассматривают
toString()
функцию-членT
выполнения поиска имени на сайте вызова вdoToString()
создания шаблона, это неверно (даже при том, что на самом деле я искал поведение в этом случае).Поведение GCC и Digital Mars выглядит корректно - в обоих случаях
toString()
функция, не являющаяся членом, связана с вызовом.Крысы - я думал, что мог бы найти умное решение, вместо этого я обнаружил пару ошибок компилятора ...
источник
Стандартное решение C ++, представленное здесь litb, не будет работать должным образом, если метод будет определен в базовом классе.
Для решения, которое обрабатывает эту ситуацию, обратитесь к:
На русском языке: http://www.rsdn.ru/forum/message/2759773.1.aspx
Перевод на английский Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
Это безумно умно. Однако одна проблема с этим решением заключается в том, что выдает ошибки компилятора, если тестируемый тип не может использоваться в качестве базового класса (например, примитивные типы).
В Visual Studio я заметил, что при работе с методом, не имеющим аргументов, необходимо добавить дополнительную пару избыточных () вокруг аргументов, чтобы вывести () в выражении sizeof.
источник
struct g { void f(); private: void f(int); };
потому что одна из функций является закрытой (это происходит потому, что код делаетusing g::f;
, что приводит к сбою, если какая-либо изf
них недоступна).MSVC имеет ключевые слова __if_exists и __if_not_exists ( Doc ). Вместе с подходом typeof-SFINAE Николая я мог бы создать проверку для GCC и MSVC, как искал OP.
Обновление: источник можно найти здесь
источник
Пример использования SFINAE и частичной специализации шаблона путем написания
Has_foo
проверки концепции:источник
Я изменил решение, представленное в https://stackoverflow.com/a/264088/2712152 чтобы сделать его более общим. Кроме того, поскольку он не использует какие-либо новые функции C ++ 11, мы можем использовать его со старыми компиляторами и также должны работать с msvc. Но компиляторы должны позволить C99 использовать это, так как он использует переменные макросы.
Следующий макрос может быть использован для проверки, имеет ли определенный класс конкретный typedef или нет.
Следующий макрос можно использовать для проверки того, имеет ли определенный класс конкретную функцию-член или нет с каким-либо заданным числом аргументов.
Мы можем использовать вышеупомянутые 2 макроса, чтобы выполнить проверки для has_typedef и has_mem_func как:
источник
HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... );
...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
Странно, никто не предложил следующий хороший трюк, который я видел однажды на этом сайте:
Вы должны убедиться, что T является классом. Кажется, что двусмысленность в поиске foo является ошибкой замещения. Я заставил его работать на gcc, но не уверен, что он стандартный.
источник
Общий шаблон, который можно использовать для проверки, поддерживается ли некоторая «функция» типом:
Шаблон, который проверяет, есть ли метод
foo
, совместимый с подписьюdouble(const char*)
Примеры
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
источник
has_foo
в шаблон вызоваis_supported
. То , что я хотел бы , чтобы назвать что - то вроде:std::cout << is_supported<magic.foo(), struct1>::value << std::endl;
. Причина этого заключается в том, что я хочу определитьhas_foo
сигнатуру для каждой отдельной функции, которую я хочу проверить, прежде чем я смогу проверить функцию?Как насчет этого решения?
источник
toString
перегружен, так как&U::toString
неоднозначно.Здесь есть много ответов, но мне не удалось найти версию, которая выполняет реальное упорядочение разрешения метода, не используя какие-либо более новые функции c ++ (только с использованием функций c ++ 98).
Примечание. Эта версия протестирована и работает с vc ++ 2013, g ++ 5.2.0 и онлайн-компилятором.
Итак, я придумал версию, которая использует только sizeof ():
Демонстрационная версия (с расширенной проверкой типов возвращаемых данных и обходным путем vc ++ 2010): http://cpp.sh/5b2vs
Нет источника, так как сам придумал.
При запуске демонстрации Live на компиляторе g ++ обратите внимание, что допустимы размеры массива 0, что означает, что используемый static_assert не будет вызывать ошибку компилятора, даже если она не будет выполнена.
Обычно используемый обходной путь заключается в замене «typedef» в макросе на «extern».
источник
static_assert(false);
). Я использовал это в связи с CRTP, где я хочу определить, имеет ли производный класс определенную функцию - что, оказывается, не работает, но ваши утверждения всегда проходили. Я потерял немного волос к этому.Вот моя версия, которая обрабатывает все возможные перегрузки функций-членов с произвольной арностью, включая функции-члены шаблона, возможно, с аргументами по умолчанию. Он различает 3 взаимоисключающих сценария при вызове функции-члена некоторого типа класса с заданными типами аргументов: (1) допустимый или (2) неоднозначный или (3) нежизнеспособный. Пример использования:
Теперь вы можете использовать это так:
Вот код, написанный на c ++ 11, однако вы можете легко перенести его (с небольшими изменениями) на не-c ++ 11, который имеет расширения typeof (например, gcc). Вы можете заменить макрос HAS_MEM своим собственным.
источник
Вы можете пропустить все метапрограммирования в C ++ 14, а просто пишу это , используя
fit::conditional
из Fit библиотеки:Вы также можете создать функцию непосредственно из лямбд:
Однако, если вы используете компилятор, который не поддерживает общие лямбда-выражения, вам придется написать отдельные функциональные объекты:
источник
fit
какой-либо другой библиотеки, кроме стандартной?С C ++ 20 вы можете написать следующее:
источник
Вот пример рабочего кода.
toStringFn<T>* = nullptr
включит функцию, которая принимает дополнительныйint
аргумент, который имеет приоритет над функцией, которая принимаетlong
при вызове с0
.Вы можете использовать тот же принцип для функций, которые возвращаются,
true
если функция реализована.источник
У меня была аналогичная проблема:
Шаблонный класс, который может быть получен из нескольких базовых классов, некоторые из которых имеют определенный член, а другие - нет.
Я решил это аналогично ответу «typeof» (Никола Бонелли), но с помощью decltype, чтобы он правильно компилировался и работал на MSVS:
источник
Еще один способ сделать это в C ++ 17 (вдохновленный boost: hana).
Напишите это один раз и используйте много раз. Это не требует
has_something<T>
типа черты классов.пример
источник
источник