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

StackOverflow https://stackoverflow.com/questions/1386183

  •  21-09-2019
  •  | 
  •  

Вопрос

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

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?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top