Подтвердить что ты не робот

Как вызвать шаблонную функцию, если она существует, и что-то еще в противном случае?

Я хочу сделать что-то вроде

template <typename T>
void foo(const T& t) {
   IF bar(t) would compile
      bar(t);
   ELSE
      baz(t);
}

Я думал, что что-то, использующее enable_if, выполнит эту работу, разделив foo на две части, но я не могу разобраться в деталях. Какой самый простой способ достичь этого?

4b9b3361

Ответ 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?