2017-10-04 7 views
0

J'ai écrit une classe de journalisation simple qui supporte les modèles variadiques en C++ en utilisant Visual Studio sous Windows. J'ai créé un modèle de fonction générique Log avec un certain nombre de spécialisations pour répondre à une combinaison commune d'entrées possibles.const_cast semble être ignoré avec les modèles C++?

#pragma once 

#include <Windows.h> 
#include <locale> 
#include <codecvt> 
#include <string> 
#include <sstream> 
#include <utility> 

using namespace std; 

inline static string to_utf8(const wstring& s) { 
    wstring_convert<codecvt_utf8_utf16<wchar_t>> utf16conv; 
    return utf16conv.to_bytes(s); 
} 

class Logger { 
public: 
    HANDLE file_handle; 
    wchar_t file_path[MAX_PATH + 1]; 

    inline Logger(HANDLE handle) : file_handle(handle), file_path{} { 
    } 

    inline Logger(const string& path) : Logger(path.c_str()) { 
    } 

    inline Logger(const wstring& path) : Logger(path.c_str()) { 
    } 

    inline Logger(const char* path) : file_handle(NULL) { 
     wstring_convert<codecvt_utf8_utf16<wchar_t>> converter; 
     wcscpy_s(file_path, MAX_PATH + 1, converter.from_bytes(path).c_str()); 
    } 

    inline Logger(const wchar_t* path) : file_handle(NULL) { 
     wcscpy_s(file_path, MAX_PATH + 1, path); 
    } 

private: 
    inline void VerifyInitialize() { 
     if ((file_handle == NULL || file_handle == INVALID_HANDLE_VALUE) && file_path[0] != '\0') { 
      file_handle = CreateFileW(file_path, FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
      SetFilePointer(file_path, 0, NULL, FILE_END); 
     } 
    } 

public: 
    inline void Log() { 
    } 

    template<typename ...Rest> 
    inline void Log(const char* first, Rest... params) { 
     VerifyInitialize(); 
     if (file_handle == NULL || file_handle == INVALID_HANDLE_VALUE) 
      return; 
     DWORD written; 
     WriteFile(file_handle, first, static_cast<DWORD>(strlen(first)), &written, NULL); 
     Log(params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const char first, Rest... params) { 
     char str[2]; 
     str[0] = first; 
     str[1] = '\0'; 
     Log(str, params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const string* first, Rest... params) { 
     VerifyInitialize(); 
     if (file_handle == NULL || file_handle == INVALID_HANDLE_VALUE) 
      return; 
     DWORD written; 
     WriteFile(file_handle, first->c_str(), static_cast<DWORD>(first->size()), &written, NULL); 
     Log(params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const string& first, Rest... params) { 
     Log(&first, params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const wstring* first, Rest... params) { 
     Log(*first, params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const wstring& first, Rest... params) { 
     Log(to_utf8(first), params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const wchar_t* first, Rest... params) { 
     Log(wstring(first), params...); 
    } 

    template<typename ...Rest> 
    inline void Log(const wchar_t first, Rest... params) { 
     wchar_t str[2]; 
     str[0] = first; 
     str[1] = '\0'; 
     Log(str, params...); 
    } 

    template<typename First, typename ...Rest> 
    inline void Log(First first, Rest... params) { 
     printf("%s\n", typeid(First).name()); 
     if (is_const<First>::value) { 
      stringstream stream; 
      stream << first; 
      Log(stream.str(), params...); 
     } else 
      Log(const_cast<const First>(first), params...); 
    } 

    inline ~Logger() { 
     if (!(file_handle == NULL || file_handle == INVALID_HANDLE_VALUE)) { 
      CloseHandle(file_handle); 
      file_handle = NULL; 
     } 
    } 
}; 

Le code fonctionne correctement avec les valeurs const. Cependant, si je présente des paramètres non-const comme ceci:

int main() { 
    Logger logger(("output.txt")); 
    wchar_t sometext[3]; 
    sometext[0] = '1'; 
    sometext[1] = '8'; 
    sometext[2] = '\0'; 
    logger.Log(sometext, L"\n"); 
    return 0; 
} 

les spécialisations ne sont pas appelés et au lieu de la dernière void Log(First first, Rest... params) générique est appelée, qui utilise stringstream et écrit le pointeur comme une chaîne au lieu de la chaîne elle-même.

Si je retire const de tous les paramètres de surcharge, quand j'invoque main() ça marche, mais si je remplace sometext avec un const char*, le dernier void Log(First first, Rest... params) générique est appelée au lieu des spécialisations (ie. La suppression const ne résout pas le problème).

Ainsi, afin d'essayer le tirer le meilleur des deux mondes, j'ai essayé d'ajouter la condition suivante:

template<typename First, typename ...Rest> 
inline void Log(First first, Rest... params) { 
    if (is_const<First>::value) { 
     stringstream stream; 
     stream << first; 
     Log(stream.str(), params...); 
    } else 
     Log(const_cast<const First>(first), params...); 
} 

avec la raison d'être de charger le compilateur de « première recherche d'une surcharge de spécialisation const , et si aucun n'a été trouvé, alors l'utilisation de stringstream ".

Cependant, je recevais un débordement de pile. Pour debug, j'ai ajouté printf("%s\n", typeid(First).name()); juste avant la condition if, et la sortie était:

wchar_t * __ptr64 
wchar_t * __ptr64 
wchar_t * __ptr64 
wchar_t * __ptr64 
.... 

Apparemment, const_cast<const First>(first) ne semble pas être en fait la coulée à const, même si is_const::value est en effet le retour false. J'ai également essayé d'utiliser std::as_const, mais il n'y avait pas de différence dans le résultat.

Pourquoi la distribution ne fonctionne-t-elle pas? Comment puis-je résoudre cela s'il vous plaît?

Répondre

1

Si First est wchar_t*, la distribution créera un wchar_t* const et non un const wchar_t*. La distribution n'est pas une substitution de texte des noms de type.

Vous créez donc un nouveau type qui ne correspond à aucune spécialisation et obtenez un appel récursif à la fonction Log générique.

+0

En plus de la spécialisation "const to pointer", j'ai ajouté une spécialisation "pointer to const" pour chaque type, et tout a fonctionné! Un million de merci :)) –

1

const_cast est uniquement utilisé pour transtyper un type de const à non-const. Et cela ne fonctionne que si la variable n'a pas été déclarée à l'origine comme const.

Vous n'avez pas besoin de quelque chose casting à Const, vous pouvez simplement copier à une référence const ou de la valeur de ce type const

const char* changeSecond(char* string, char change) 
{ 
    string[0] = change; 
    return string; 
} 

Cette fonction retourne une const char*, sans coulée nécessaire. Cela peut également être fait avec affectation.