J'ai deux fonctions suivantes:

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

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

Les deux appellent une fonction C qui accepte const char * ou const wchar_t * et ont respectivement les suffixes U ou W. Je voudrais créer une fonction de modèle pour gérer ces deux cas. J'ai essayé la tentative suivante:

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());
}

Mais cela ne semble pas fonctionner correctement. Si j'appelle:

foo("abc");

Cela ne compilera pas. Pourquoi donc? pourquoi un compilateur n'est pas capable de déduire le type approprié T à char? Est-il possible de créer une fonction qui gérerait à la fois std :: string et std :: wstring?

3
Igor 20 nov. 2018 à 21:53

4 réponses

Meilleure réponse

cela ne compilera pas. Pourquoi donc? pourquoi un compilateur n'est pas capable de déduire le type T approprié à char?

Comme mieux expliqué par d'autres, "abc" est un char[4], donc est convertible en std::basic_string<char> mais n'est pas un std::basic_string<char>, donc on ne peut pas en déduire le {{X4 }} tapez char pour une fonction de modèle qui accepte un std::basic_string<T>.

Est-il possible de créer une fonction qui gérerait à la fois std :: string et std :: wstring?

Oui c'est possible; mais quel est le problème avec votre solution de surcharge à deux fonctions?

Quoi qu'il en soit, si vous voulez vraiment une seule fonction et si vous acceptez d'écrire beaucoup de casuistique, je suppose que vous pouvez écrire quelque chose comme suit

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 ?
}

Ou, un peu plus synthétique mais moins efficace

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 ?
}

Vous devriez donc pouvoir appeler foo() avec std::string, std::wstring, char *, wchar_t *, char[] ou wchar_t[].

5
max66 20 nov. 2018 à 19:33

Une solution de contournement dans C ++ 17 est:

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());
}

Et pour éviter le problème mentionné par Justin à propos de la chaîne non terminée par null

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());
}
2
Jarod42 20 nov. 2018 à 19:21

Oui, il existe un type, c'est-à-dire std::basic_string<char>, qui peut être initialisé par copie à partir de l'expression "abc". Vous pouvez donc appeler une fonction comme void foo(std::basic_string<char>) avec l'argument "abc".

Et non, vous ne pouvez pas appeler un modèle de fonction template <class T> void foo(const std::basic_string<T> &s) avec l'argument "abc". Parce que pour déterminer si le paramètre peut être initialisé par l'argument, le compilateur doit d'abord déterminer le paramètre de modèle T. Il essaiera de faire correspondre const std::basic_string<T> & contre const char [4]. Et cela échouera.

La raison pour laquelle il échouera est à cause de la règle de déduction des arguments de modèle. La règle actuelle est très compliquée. Mais dans ce cas, pour que std::basic_string<char> soit examiné lors de la déduction, le compilateur devra rechercher un "constructeur de conversion" approprié, c'est-à-dire le constructeur qui peut être appelé implicitement avec l'argument "abc", et une telle recherche n'est pas autorisé par la norme lors de la déduction.

Oui, il est possible de gérer std :: string et std :: wstring dans un modèle de fonction:

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));
}
2
felix 20 nov. 2018 à 19:33

Le problème ici est que dans foo("abc");, "abc" n'est pas un std::string ou un std::wstring, c'est un const char[N]. Comme ce n'est pas un std::string ou un std::wstring, le compilateur ne peut pas déduire ce que T devrait être et il échoue à se compiler. La solution la plus simple est d'utiliser ce que vous avez déjà. Les surcharges seront prises en compte et il est préférable de convertir "abc" en std::string pour qu'il appelle cette version de la fonction.

Si vous le souhaitez, vous pouvez utiliser un std::string_view / {{X1 }} au lieu de std::string / std::wstring donc vous n'allouez pas de mémoire si vous transmettez à la fonction une chaîne littérale. Cela changerait les surcharges en

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

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

Notez que std::basic_string_view peut être construit sans avoir de terminateur nul, il est donc possible de passer un std::basic_string_view qui ne remplira pas l'exigence de chaîne C terminée par null que votre fonction C. Dans ce cas, le code a un comportement indéfini.

4
NathanOliver 20 nov. 2018 à 19:17