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

Функция вызова на основе типа аргумента шаблона

Существуют две функции "C":

void fooA(const char*);
void fooW(const wchar_t*);

Затем существует функция шаблона-обертки:

template<typename _TChar>
void foo(const _TChar* str)
{
     // call fooA or fooB based on actual type of _TChar
     // std::conditional .. ?
         // fooA(str); 
         // fooW(str);
}

Если вызывающий абонент вызывает foo("Abc"), эта функция шаблона должна сделать вызов времени компиляции fooA. Аналогично, foo(L"Abc") должен сделать окончательный вызов fooW.

Как мне это сделать? Я думал об использовании std::conditional, но не смог этого сделать.

Я не могу перегрузить fooA или fooB, так как это C-функции.

4b9b3361

Ответ 1

Вы можете разместить все свои версии wchar_t в шаблоне класса, скажем overloads и их контр-элементы char по своей специализации, как показано ниже:

template<typename WideCharVersion> 
struct overloads
{
    void foo(wchar_t const * arg)
    {
       FooW(arg);
    }
    //more wchar_t functions
};

template<> 
struct overloads<std::false_type>
{
    void foo(char const * arg)
    {
       FooA(arg);
    }
    //more char functions
};

//a friendly alias!
template<typename T>
using is_wide_char = typename std::is_same<whar_t, T>::type;

И затем вы можете использовать их как:

template<typename _TChar>
void foo(const _TChar* str)
{
    overloads<is_wide_char<_TChar>>::foo(str);
}

Выражение SFINAE упрощает работу!

Другим способом является использование Expression SFINAE, которое не требует, чтобы вы пишете что-либо вроде overloads и выполняет ту же работу с меньшим количеством кода:

template<typename _TChar>
void foo(const _TChar* str)
{
    invokeOne(fooA, fooW, str);
}

И тогда вы можете реализовать invokeOne как:

 template<typename F1, typename F2, typename ... Args>
 auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f1(args...))
 {
     return f1(args...);
 }

 template<typename F1, typename F2, typename ... Args>
 auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f2(args...))
 {
     return f2(args...);
 }

Посмотрите онлайн-демонстрацию.

В этом подходе вам не нужно добавлять перегрузки к шаблону класса overloads и к его специализации. Вместо этого вы просто передаете их в качестве аргументов invokeOne, который вызывает правильную перегрузку для вас.

Надеюсь, что это поможет.

Ответ 2

Затем перегрузите другую функцию. Я предполагаю, что foo делает больше работы и должен быть шаблоном. Затем вызовите foo_forward_call, определяемый как таковой:

void foo_forward_call(char const* ptr) {
    FooA(ptr);
}

void foo_forward_call(wchar_t const* ptr) {
    FooW(ptr);
}

и на сайте вызова:

template<typename _TChar>
void foo(const _TChar* str)
{
    foo_forward_call(str);
}

в С++ 1z вы сможете использовать constexpr if, но, честно говоря, я думаю, что перегруженное решение еще более читаемо.

template<typename _TChar>
void foo(const _TChar* str)
{
    if constexpr(std::is_same<_TChar, char>::value) {
        FooA(str);
    } else {
        FooW(str);
    }
}

В качестве альтернативы вы можете использовать Boost.Hana overload:

template<typename _TChar>
void foo(const _TChar* str)
{
    hana::overload(fooA, fooW)(str);
}

демо


Кстати: вам следует избегать использования имен подчеркивания-заглавных букв в ваших программах. Они зарезервированы для реализации для любого использования (т.е. Макросов) и могут привести к неприятным столкновениям имен.

Ответ 3

Это похоже на действительно странную вещь, связанную с шаблонами. Вместо этого я бы предложил использовать обычную перегрузку:

void foo(const char* p) { fooA(p); }
void foo(const wchar_t* p) { fooW(p); }

Если вы настаиваете на использовании шаблона, вы можете сделать это следующим образом:

template <typename T>
void foo(const T* p)
{
    // Declare functions here so that calling fooW with const char*
    // and 'calling' fooA with const wchar_t* would not cause compile error.
    void fooA(const T*);
    void fooW(const T*);

    if (std::is_same<char, T>::value)
        fooA(p);
    else
        fooW(p);
}

Ответ 4

Мне нравится решать проблемы в целом. Итак, давайте разработаем механизм перегрузки.

overload_t<...> принимает набор вызовов в ... и генерирует объект, который использует стандартное разрешение перегрузки для выбора между ними через наследование operator():

template<class...Fs>
struct overload_t;
// the case where we have a function object:
template<class F>
struct overload_t<F>:F{
  overload_t(F f):F(std::move(f)){}
  using F::operator();
  // boilerplate to ensure these are enabled if possible:
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// we cannot inherit from a function pointer.  So
// store one, and write an `operator()` that forwards to it:
template<class R, class...Args>
struct overload_t<R(*)(Args...)>{
  using F=R(*)(Args...);
  F f;
  overload_t(F fin):f(fin){}
  R operator()(Args...args)const{
    return f(std::forward<Args>(args)...);
  }
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// the case where we have more than type to overload.
// recursively inherit from the one-arg and the rest-of-arg
// and using operator() to bring both of their () into equal standing:
template<class F0, class...Fs>
struct overload_t<F0,Fs...>:
  overload_t<F0>,
  overload_t<Fs...>
{
  using overload_t<F0>::operator();
  using overload_t<Fs...>::operator();
  overload_t(F0 f0, Fs...fs):
    overload_t<F0>(std::move(f0)),
    overload_t<Fs...>(std::move(fs)...)
  {}
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// a helper function to create an overload set without
// having to specify types.  Will be obsolete in C++17:
template<class...Fs>
overload_t<Fs...> overload(Fs...fs){ return {std::move(fs)...};}

Теперь для создания одного объекта, который является перегрузкой нескольких, сделайте следующее:

overload(FooA,FooW)( str );

И str будет отправлено либо на основе обычных правил разрешения перегрузки. Это полезно в других местах, поэтому его стоит написать, а код в точке использования сам документируется.

Пример в реальном времени (ничего себе, написав это правильно в первый раз!)

Существует ряд улучшений, которые можно добавить к вышеперечисленному overload_t.

  • Идеальная пересылка f во время построения и в вспомогательную функцию.

  • Сбалансированное наследование двоичного дерева вместо линейного (важно для более чем нескольких перегрузок). Это может означать выполнение выполнения и компиляции, особенно для большого числа функций.

  • Introspect incoming Fs; если они overload_t балансируют объединенное дерево.

  • В С++ 17, a func<auto> шаблон, который принимает указатель на функцию и возвращает объект функции, который без него вызывает его. Компиляторы относительно хороши при указании указателей на функции, но они лучше подходят для него, когда нет возможного состояния времени выполнения, которое могло бы их изменить.

  • Решение, что делать для overload_t<>. В настоящее время он не компилируется; возможно, это просто пустой struct {} или даже структура с невосстанавливаемым operator().

  • Изучите существующие библиотеки, например boost::hana::overload и посмотрите, какие отличия существуют.

  • Выявить возможность извлечения какой из перегрузок будет вызываться, возможно, с помощью метода static tag_t<F> which_overload_helper( Args... ) const и template<class...Args> using which_overload = typename decltype( which_overload_helper( std::declval<Args>()... ) )::type;

  • Правильный выбор перегрузок, когда некоторые входящие Fs do/не имеют const operator(). Если указатель функции имеет const, volatile, оба или ни на operator()? Все 4? Как насчет && vs & перегрузок?

Ответ 5

Возможно, если было больше целевых функций или перегрузка была не единственной проблемой, которую вы пытались решить, тогда было бы полезно использовать шаблоны, чтобы это сделать.

В этом случае, просто напишите свои перегрузки:

void foo(const char* x) { fooA(x); }
void foo(const wchar_t* x) { fooW(x); }

Ответ 6

У вас есть несколько вариантов.

  • Используйте помощник, явно специализированный struct:

    template <typename>
    struct helper;
    
    template<>
    struct helper<char>
    {
        void operator()(const char* x){ FooA(x); }
    };
    
    template<>
    struct helper<wchar_t>
    {
        void operator()(const wchar_t* x){ FooW(x); }
    };
    
    template <typename _TChar>
    void foo(const _TChar* str)
    {
        helper<_TChar>{}(str);
    }
    
  • Используйте реализацию "static if" (например, boost:: hana:: eval_if или мой собственный):

    template <typename _TChar>
    void foo(const _TChar* str)
    {
        vrm::core::static_if(std::is_same<_TChar, char>{})
            .then([](const auto* x_str){ FooA(x_str); })
            .else_([](const auto* x_str){ FooW(x_str); })(str);
    }
    
  • Используйте вспомогательную перегруженную функцию:

    void helper(const char* x) { FooA(x); }
    void helper(const wchar_t* x) { FooW(x); }
    
    template <typename _TChar>
    void foo(const _TChar* str)
    {
        helper(str);
    }