Как вызвать шаблонную функцию, если она существует, и что-то еще в противном случае?
Вопрос
Я хочу сделать что-то вроде
template <typename T>
void foo(const T& t) {
IF bar(t) would compile
bar(t);
ELSE
baz(t);
}
Я думал, что что-то используя enable_if
сделал бы всю работу здесь, разделившись foo
на две части, но я не могу разобраться в деталях.Какой самый простой способ добиться этого?
Решение
Для имени выполняются два поиска. 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
В качестве второго аргумента будет возвращен тип, размер которого равен 1, если был выбран наш резервный вариант, и размер, больший 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);
}
Другие советы
литб дал вам очень хороший ответ.Однако мне интересно, не могли бы мы, учитывая больше контекста, придумать что-то менее общее, но и менее, хм, разрабатывать?
Например, какие типы могут быть 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
может быть немного проще.Но это я не исследовал, так что это всего лишь мысль.
Я думаю, что решение ЛитБ работает, но оно слишком сложное.Причина в том, что он вводит функцию 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);
}
Но, как я указал в комментарии к исходному сообщению Литба, есть много причин, почему bar(t)
может не скомпилироваться, и я не уверен, что это решение обрабатывает те же случаи.Это определенно потерпит неудачу private bar::bar(T t)
Если вы готовы ограничиться Visual C++, вы можете использовать __if_exists и __if_not_exists заявления.
Удобно в крайнем случае, но зависит от платформы.
РЕДАКТИРОВАТЬ:Я говорил слишком рано! ответ Литба показывает, как это на самом деле можно сделать (возможно, ценой вашего здравомыслия...:-П)
К сожалению, я думаю, что общий случай проверки того, «будет ли это скомпилировано», недоступен. выведение аргументов шаблона функции + SFINAE, что является обычным трюком для этого материала.Я думаю, лучшее, что вы можете сделать, это создать шаблон функции «резервного копирования»:
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
продвижение, хотя последний бы скомпилировался и работал нормально!
Суммируя:простого и полного решения не существует, и я почти уверен, что не существует даже чертовски сложного и полного решения.:(
//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);
}
Разве вы не можете использовать здесь полную специализацию (или перегрузку) на foo.Скажем, иметь панель вызова шаблона функции, но для определенных типов полностью специализировать ее для вызова baz?