Как вызвать шаблонную функцию, если она существует, и что-то еще в противном случае?
Я хочу сделать что-то вроде
template <typename T>
void foo(const T& t) {
IF bar(t) would compile
bar(t);
ELSE
baz(t);
}
Я думал, что что-то, использующее enable_if
, выполнит эту работу, разделив foo
на две части, но я не могу разобраться в деталях. Какой самый простой способ достичь этого?
Ответы
Ответ 1
Существует два поиска, которые выполняются для имени bar
. Одним из них является неквалифицированный поиск в контексте определения foo
. Другой - зависящий от аргументов поиск в каждом контексте контекста (но результат поиска в каждом контексте экземпляра не позволяет изменять поведение между двумя различными контекстами создания контекста).
Чтобы получить желаемое поведение, вы можете пойти и определить резервную функцию в пространстве имен fallback
, которая возвращает уникальный тип
namespace fallback {
// sizeof > 1
struct flag { char c[2]; };
flag bar(...);
}
Функция bar
будет вызываться, если ничего не соответствует, потому что у многоточия есть худшая стоимость преобразования. Теперь включите этих кандидатов в свою функцию с помощью директивы using fallback
, так что fallback::bar
будет включен как кандидат в вызов bar
.
Теперь, чтобы узнать, разрешает ли вызов bar
вашей функции, вы вызовете его и проверьте, является ли тип возврата flag
. Тип возврата для выбранной функции может быть недействительным, поэтому вам нужно сделать некоторые трюки оператора запятой, чтобы обойти это.
namespace fallback {
int operator,(flag, flag);
// map everything else to void
template<typename T>
void operator,(flag, T const&);
// sizeof 1
char operator,(int, flag);
}
Если бы наша функция была выбрана, вызов оператора запятой вернет ссылку на int
. Если нет, или если выбранная функция возвратила void
, то вызов возвращает void
по очереди. Затем следующий вызов с flag
в качестве второго аргумента будет возвращать тип с sizeof 1, если был выбран наш резерв, и sizeof больше 1 (встроенный оператор запятой будет использоваться, потому что void
находится в миксе), если выбрано другое.
Мы сравниваем sizeof и делегат с структурой.
template<bool>
struct foo_impl;
/* bar available */
template<>
struct foo_impl<true> {
template<typename T>
static void foo(T const &t) {
bar(t);
}
};
/* bar not available */
template<>
struct foo_impl<false> {
template<typename T>
static void foo(T const&) {
std::cout << "not available, calling baz...";
}
};
template <typename T>
void foo(const T& t) {
using namespace fallback;
foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
::foo(t);
}
Это решение неоднозначно, если существующая функция также имеет многоточие. Но это кажется маловероятным. Проверка с использованием резервной копии:
struct C { };
int main() {
// => "not available, calling baz..."
foo(C());
}
И если кандидат найден с использованием зависимого от аргумента поиска
struct C { };
void bar(C) {
std::cout << "called!";
}
int main() {
// => "called!"
foo(C());
}
Чтобы проверить неквалифицированный поиск в контексте определения, определите следующую функцию выше foo_impl
и foo
(поместите шаблон foo_impl выше foo
, поэтому они имеют одинаковый контекст определения)
void bar(double d) {
std::cout << "bar(double) called!";
}
// ... foo template ...
int main() {
// => "bar(double) called!"
foo(12);
}
Ответ 2
litb дал вам очень хороший ответ. Тем не менее, я задаюсь вопросом, не могли ли мы придумать что-то более обобщенное, но также и меньше, гм?
Например, какие типы могут быть T
? Что-нибудь? Несколько типов? Очень ограниченный набор, который вы контролируете? Некоторые классы, которые вы разрабатываете совместно с функцией foo
? Учитывая последнее, вы можете просто поставить что-то вроде
typedef boolean<true> has_bar_func;
в типы, а затем переключитесь на разные перегрузки foo
на основе этого:
template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);
template <typename T>
void foo(const T& t) {
foo_impl( t, typename T::has_bar_func() );
}
Кроме того, может ли функция bar
/baz
иметь практически любую подпись, есть ли несколько ограниченный набор или есть только одна действительная подпись? Если последняя, отличная идея возврата, в сочетании с мета-функцией, использующей sizeof
, может быть немного проще. Но этого я не изучил, так что это просто мысль.
Ответ 3
Я думаю, что латентное решение работает, но слишком сложно. Причина в том, что он вводит функцию fallback::bar(...)
, которая действует как "функция последней инстанции", а затем идет на большие длины, чтобы не называть ее. Зачем? Кажется, у нас есть идеальное поведение для него:
namespace fallback {
template<typename T>
inline void bar(T const& t, ...)
{
baz(t);
}
}
template<typename T>
void foo(T const& t)
{
using namespace fallback;
bar(t);
}
Но, как я указал в комментарии к оригинальному сообщению litb, существует множество причин, по которым bar(t)
может не скомпилироваться, и я не уверен, что это решение обрабатывает те же случаи. Это, безусловно, потерпит неудачу на private bar::bar(T t)
Ответ 4
РЕДАКТОР: Я говорил слишком рано! litb answer показывает, как это можно сделать (при возможной стоимости вашего здравомыслия...: -P)
К сожалению, я думаю, что общий случай проверки "будет ли этот компилятор" недоступен вывод аргумента шаблона функции + SFINAE, который обычный трюк для этого материала. Я думаю, что лучше всего вы можете создать шаблон функции "backup":
template <typename T>
void bar(T t) { // "Backup" bar() template
baz(t);
}
И затем измените foo()
на просто:
template <typename T>
void foo(const T& t) {
bar(t);
}
Это будет работать в большинстве случаев. Поскольку тип параметра шаблона bar()
равен T
, он будет считаться "менее специализированным" по сравнению с любым другим шаблоном функции или функции с именем bar()
и поэтому уступит приоритет этой ранее существующей функции или шаблону функции во время перегрузки разрешающая способность. Кроме того:
- Если ранее существовавший
bar()
сам является шаблоном функции, принимающим параметр шаблона типа T
, возникает неоднозначность, потому что ни один шаблон не является более специализированным, чем другой, и компилятор будет жаловаться.
- Неявные преобразования также не будут работать, и это приведет к затрудненным диагностированию проблем: предположим, что существует уже существовавший
bar(long)
, но foo(123)
вызывается. В этом случае компилятор будет спокойно выбирать экземпляр шаблона bar()
T = int
вместо выполнения int->long
продвижения, хотя последний скомпилировался и работал нормально!
Короче: нет простого, полного решения, и я уверен, что нет даже сложного решения.: (
Ответ 5
Если вы хотите ограничить себя Visual С++, вы можете использовать __ if_exists и __ if_not_exists.
Удобный в использовании, но специфичный для платформы.
Ответ 6
//default
//////////////////////////////////////////
template <class T>
void foo(const T& t){
baz(t);
}
//specializations
//////////////////////////////////////////
template <>
void foo(const specialization_1& t){
bar(t);
}
....
template <>
void foo(const specialization_n& t){
bar(t);
}
Ответ 7
Вы не можете использовать полную специализацию здесь (или перегрузку) на foo. Говоря, имея панель вызова шаблона функции, но для определенных типов полностью специализируется на вызове baz?