Использование шаблона для обработки строк и строк - PullRequest
0 голосов
/ 20 ноября 2018

У меня есть две следующие функции:

void bar(const std::string &s)
{
    someCFunctionU(s.c_str());
}

void bar(const std::wstring &s)
{
    someCFunctionW(s.c_str());
}

Обе они вызывают некоторую функцию C, которая принимает const char * или const wchar_t * и имеют суффиксы U или W соответственно.Я хотел бы создать функцию шаблона для обработки обоих этих случаев.Я попробовал следующую попытку:

template <typename T>
void foo(const std::basic_string<T> &s)
{
    if constexpr (std::is_same_v<T, char>)
        someCFunctionU(s.c_str());
    else
        someCFunctionW(s.c_str());
}

Но, похоже, это работает неправильно.Если я позвоню:

foo("abc");

, это не скомпилируется.Это почему?почему компилятор не может вывести правильный тип от T до char?Можно ли создать одну функцию, которая будет обрабатывать как std :: string и std :: wstring?

Ответы [ 4 ]

0 голосов
/ 20 ноября 2018

это не скомпилируется.Это почему?почему компилятор не может вывести правильный тип T на тип char?

Как лучше объяснено другими, "abc" - это char[4], поэтому он конвертируется в std::basic_string<char>, но не 't * std::basic_string<char>, поэтому нельзя вывести тип T как char для функции шаблона, которая принимает std::basic_string<T>.

Возможно ли создать одну функцию, которая быобрабатывать как std :: string и std :: wstring?

Да, это возможно;но что не так с вашим решением с двумя функциями при перегрузке?

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

template <typename T>
void foo (T const & s)
{
    if constexpr ( std::is_same_v<T, std::string> )
        someCFunctionU(s.c_str());                       
    else if constexpr ( std::is_convertible_v<T, char const *>)
        someCFunctionU(s);
    else if constexpr ( std::is_same_v<T, std::wstring> )
        someCFunctionW(s.c_str());
    else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
        someCFunctionW(s);
    // else exception ?
}

или, немного более синтетически, но менее эффективно

template <typename T>
void foo (T const & s)
{
    if constexpr ( std::is_convertible_v<T, std::string> )
        someCFunctionU(std::string{s}.c_str());
    else if constexpr (std::is_convertible_v<T, std::wstring> )
        someCFunctionW(std::wstring{s}.c_str());
    // else exception ?
}

Таким образом, вы сможете звонить foo() с std::string, std::wstring, char *, wchar_t *, char[] или wchar_t[].

0 голосов
/ 20 ноября 2018

Да, существует тип, т.е. std::basic_string<char>, который можно скопировать инициализировать из выражения "abc".Таким образом, вы можете вызвать функцию типа void foo(std::basic_string<char>) с аргументом "abc".

И нет, вы не можете вызвать шаблон функции template <class T> void foo(const std::basic_string<T> &s) с аргументом "abc".Потому что для того, чтобы выяснить, может ли параметр быть инициализирован аргументом, компилятору необходимо сначала определить параметр шаблона T.Он попытается сопоставить const std::basic_string<T> & с const char [4].И он потерпит неудачу.

Причина, по которой он потерпит неудачу, заключается в правиле вывода аргументов шаблона.Фактическое правило очень сложное.Но в этом случае, для проверки std::basic_string<char> во время вывода, компилятору нужно будет искать правильный «конвертирующий конструктор», то есть конструктор, который можно вызывать неявно с аргументом "abc", и такой поиск не разрешенпо стандарту при выводе.

Да, можно обрабатывать std :: string и std :: wstring в одном шаблоне функции:

void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}

template <class T>
auto foo(T &&t) {
    return foo_impl(std::forward<T>(t));
}
0 голосов
/ 20 ноября 2018

Обходной путь в C ++ 17:

template <typename T>
void foo(const T &s)
{
    std::basic_string_view sv{s}; // Class template argument deduction

    if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
        someCFunctionU(sv.data());
    else
        someCFunctionW(sv.data());
}

И чтобы избежать проблемы, упомянутой Джастином по поводу строки с ненулевым символом в конце

template <typename T> struct is_basic_string_view : std::false_type {};

template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};

template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
    std::basic_string_view sv{s}; // Class template argument deduction

    if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
        someCFunctionU(sv.data());
    else
        someCFunctionW(sv.data());
}
0 голосов
/ 20 ноября 2018

Проблема здесь в том, что в foo("abc");, "abc" это не std::string или std::wstring, это const char[N].Поскольку это не std::string или std::wstring, компилятор не может определить, каким должен быть T, и он не может скомпилироваться.Самое простое решение - использовать то, что у вас уже есть.Перегрузки будут учтены, и лучше всего преобразовать "abc" в std::string, поэтому он вызовет эту версию функции.

Если вы хотите, вы можете использовать std::string_view / std::wstring_view вместо std::string / std::wstring, поэтому вы фактически не выделяете память, если передаете функции строковый литерал.Это изменило бы перегрузки на

void bar(std::string_view s)
{
    someCFunctionU(s.data());
}

void bar(std::wstring_view s)
{
    someCFunctionW(s.data());
}

Обратите внимание, что std::basic_string_view может быть создан без использования нулевого терминатора, так что можно передать std::basic_string_view, который не будет выполнять нулевую завершенную c-строкутребование, чтобы ваша функция C имела.В этом случае код имеет неопределенное поведение.

...