Если Вы хотите удалить предел, предупреждающий в целом, что можно взять следующие шаги:
Sources:
Для имени 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
. Затем следующий вызов с флагом
в качестве второго аргумента вернет тип с размером 1, если был выбран наш запасной вариант, и размер больше 1 (будет использоваться встроенный оператор запятой, потому что в миксе есть void
), если было выбрано что-то еще.
Мы сравниваем sizeof и delegate со структурой.
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);
}
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
имеет практически любую подпись, есть ли несколько ограниченный набор или только одна действительная подпись? Если последнее, то запасная идея litb (отличная), в сочетании с метафункцией, использующей sizeof
, может быть немного проще. Но это я не исследовал, так что это просто мысль.
Я думаю, что решение litb работает, но слишком сложно. Причина в том, что он вводит функцию 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)
может не компилироваться, и я не уверен, что это решение обрабатывает те же случаи. Это определенно не сработает на приватном баре :: bar (T t)
Если вы хотите ограничиться Visual C ++, вы можете использовать __ if_exists и __ if_not_exists .
Удобно в крайнем случае, но зависит от платформы.
РЕДАКТИРОВАТЬ: Я заговорил слишком рано! ответ litb показывает, как это можно сделать на самом деле (возможной ценой вашего здравомыслия ... :-P)
К сожалению, я думаю, что общий случай проверки "будет ли эта компиляция" недоступен из вывода аргументов шаблона функции + 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
, возникнет двусмысленность, потому что ни один шаблон не является более специализированным, чем другой, и компилятор будет жаловаться. полоса (длинная)
, но вызывается foo (123)
. В этом случае компилятор незаметно выберет создание экземпляра шаблона «backup» bar ()
с T = int
вместо выполнения int-> долгое
продвижение, даже если последний компилировался и работал бы нормально! Короче говоря: нет простого, полного решения, и я почти уверен, что нет даже чертовски сложного, полного решения. : (
//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?