2010-01-11 24 views
18

¿Es posible crear un constructor (o firma de función, para el caso) que solo acepta una cadena literal, pero no una letra p. char const *?C++: Constructor aceptando solo una cadena literal

¿Es posible tener dos sobrecargas que puedan distinguir entre literales de cadena y char const *?

C++ 0x permitiría esto con un sufijo personalizado, pero estoy buscando una solución "anterior".

Justificación: evitando la copia del montón de cadenas que no se modificarán cuando se den como literales de cadena.

Estas cadenas van directamente a una API esperando un const char * sin ningún procesamiento. La mayoría de las llamadas usan literales que no requieren procesamiento adicional, solo que en unos pocos casos se construyen. Estoy buscando la posibilidad de preservar el comportamiento de llamadas nativas.

Nota: - ya que viene en las respuestas: el código en cuestión no utiliza std::string en absoluto, pero es un buen ejemplo sería:

class foo 
{ 
    std::string m_str; 
    char const * m_cstr;  
public: 
    foo(<string literal> s) : m_cstr(p) {} 
    foo(char const * s) : m_str(s) { m_cstr = s.c_str(); } 
    foo(std::string const & s) : m_str(s) { m_cstr = s.c_str(); } 

    operator char const *() const { return m_cstr; } 
} 

Resultados:

(1) no se puede hacer.
(2) Me di cuenta de que ni siquiera estoy buscando un literal, sino una constante de tiempo de compilación (es decir, "todo lo que no necesita copiarse").

que probablemente utilice el siguiente patrón en su lugar:

const literal str_Ophelia = "Ophelia"; 

void Foo() 
{ 
    Hamlet(str_Ophelia, ...); // can receive literal or string or const char * 
} 

con un simple

struct literal 
{ 
    char const * data; 
    literal(char const * p) : data(p) {} 
    operator const char *() const { return data; } 
}; 

que no se detiene a nadie de abusar de ella (que debería encontrar un mejor nombre ...) , pero permite la optimización requerida, pero permanece seguro por defecto.

+0

Yo no lo creo, ya que 'string' tiene un constructor que toma un 'char const *'. –

+0

¿Podría explicar el razonamiento? No puedo entender por qué querría copiar un literal de cadena, pero NO copiar un 'const char *' (o al revés). No puedes cambiar ambos exactamente de la misma manera. – Hexagon

+0

También deberá mantener un indicador y tener un código condicional en su destructor. Parece que tendrías que crear muchos más objetos usando cadenas literales de lo que normalmente hago para que esto valga la pena. –

Respuesta

9

No, simplemente no puede hacer esto: los literales de cadena y const char * son intercambiables. Una solución podría ser introducir una clase especial para contener punteros a literales de cadena y hacer que un constructor solo acepte eso. De esta manera, cuando necesite pasar un literal, llame a un constructor de esa clase y pase el objeto temporal. Esto no evita por completo el mal uso, pero hace que el código sea mucho más fácil de mantener.

+0

¿Sigue siendo cierto para C++ 14? Hmm, ¿tal vez podrían usarse literales definidos por el usuario? http://en.cppreference.com/w/cpp/language/user_literal – panzi

3

Si sabe exactamente cómo su compilador y plataforma se ocupan de los literales de cadenas, podría ser posible escribir una solución que pueda hacer esto. Si sabe que su compilador siempre coloca literales de cadena en una región específica de la memoria, puede verificar el puntero contra los límites de esa memoria. Si cae dentro de ese bloque, tienes una cadena literal; de lo contrario, tienes una cadena almacenada en el montón o la pila.

Sin embargo, esta solución sería específica de plataforma/compilador. No sería portátil.

+0

probablemente sea la única forma "segura" de hacerlo, pero no quiero confiar en ese truco tan poco práctico. ¡Gracias de todos modos! – peterchen

19

Solución de trabajo basado en sbi idea:

struct char_wrapper 
{ 
    char_wrapper(const char* val) : val(val) {}; 
    const char* val; 
}; 

class MyClass { 
public: 
    template< std::size_t N > 
    explicit MyClass(const char (&str)[N]) 
    { 
     cout << "LITERAL" << endl; 
    } 
    template< std::size_t N > 
    explicit MyClass(char (&str)[N]) 
    { 
     cout << "pointer" << endl; 
    }  
    MyClass(char_wrapper m) 
    { 
    cout << "pointer" << endl; 
    } 
}; 

int main() 
{ 
    MyClass z("TEST1");  // LITERAL 
    const char* b = "fff"; 
    MyClass a(b);   // pointer 
    char tmp[256]; 
    strcpy(tmp, "hello"); 
    MyClass c(tmp);   // pointer 
} 
+2

@Downvoter: ¿me gustaría comentar? ¿Con qué poco estás en desacuerdo? –

+1

Probablemente el hecho de que no funciona: reconoce las matrices de caracteres como literales de cadena para empezar. –

+0

, sin embargo, buena idea para evitar el problema de la resolución de sobrecarga. – peterchen

0

En algunas plataformas, he tenido que declarar literales de cadena como static const char * para que el programa para acceder al texto de la memoria de sólo lectura. Cuando se declaró como const char *, la lista de ensamblado mostró que el texto se copió de la ROM en una variable de pila.

En lugar de preocuparse por el receptor, quizás intente declarar los literales de cadena con static const char *.

0

con un nuevo literales definidos por el usuario en C++ 14 (como para Clang 3.5 - que funciona con C++ 11 también), hay una solución elegante:

class Literal { 
public: 
    explicit Literal(const char* literal) : literal_(literal) {} 
    // The constructor is public to allow explicit conversion of external string 
    // literals to `_L` literals. If there is no such need, then move constructor 
    // to private section. 

    operator const char*() { return literal_; } 

private: 
    friend Literal operator"" _L (const char*, unsigned long); 
    // Helps, when constructor is moved to private section. 

    const char* literal_; 
}; 

Literal operator"" _L (const char* str, unsigned long) { 
    return Literal(str); 
} 

Se puede utilizar como esto:

void f1(Literal) {} // Accepts literals only. 

int main() { 
    auto str1 = "OMG! Teh Rey!"_L; 
    std::cout << str1 << std::endl; 
    f(str1); 
} 

hay un inconveniente: hay que añadir a cada _L literal - pero no es un gran problema, de verdad.

17

Sí, es puede hacerse! Se me ocurrió una solución que funciona con C++ 03 y sin una clase contenedora (que rompe algunas conversiones implícitas en declaraciones de devolución).

En primer lugar, necesita una plantilla de constructor para los tipos const char (&)[N], ya que este es el tipo original de literales de cadena. Entonces también necesita otro para los tipos char (&)[N], como búfers de char, por ejemplo, para que no terminen en el constructor de literales. Y probablemente también desee un constructor normal para el tipo const char*.

template<int N> Foo(const char (&)[N]); // for string literals 
template<int N> Foo(char (&)[N]);  // for non-const char arrays like buffers 
Foo(const char*);      // normal c strings 

El problema ahora es que, para la cadena literales el compilador sigue pensando, que el const char* constructor es una mejor opción que una instancia de plantilla, ya que conversiones-array-a puntero tienen rango de coincidencia exacta . (13.3.3.1.1)

Por lo tanto, el truco es reducir la precedencia del constructor const char*. Esto se puede hacer cambiándolo también a una plantilla y utilizando SFINAE para que coincida solo con el tipo const char*. El constructor no tiene valor de retorno y solo un parámetro, que es necesario para la deducción del tipo. Por lo tanto otro "parámetro ficticio" con un valor por defecto es necesario, que utiliza el tipo de rasgo condicional: template<typename T> Foo(T, typename IsCharPtr<T>::Type=0)

Solución:

#include <iostream> 

#define BARK std::cout << __PRETTY_FUNCTION__ << std::endl 

struct Dummy {}; 
template<typename T> struct IsCharPtr {}; 
template<> struct IsCharPtr<const char *> { typedef Dummy* Type; }; 
template<> struct IsCharPtr<char *> { typedef Dummy* Type; }; 

struct Foo { 
    template<int N> Foo(const char (&)[N]) { BARK; } 
    template<int N> Foo(char (&)[N]) { BARK; } 
    template<typename T> Foo(T, typename IsCharPtr<T>::Type=0) { BARK; } 
}; 

const char a[] = "x"; 
const char* b = "x"; 
const char* f() { return b; } 

int main() { 
    char buffer[10] = "lkj"; 
    char* c = buffer; 
    Foo l("x");  // Foo::Foo(const char (&)[N]) [N = 2] 
    Foo aa(a);  // Foo::Foo(const char (&)[N]) [N = 2] 
    Foo bb(b);  // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = const char *] 
    Foo cc(c);  // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = char *] 
    Foo ee(buffer); // Foo::Foo(char (&)[N]) [N = 10] 
    Foo ff(f()); // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = const char *] 
    return 0; 
} 
+0

Creo que esto debería ser aceptado como la respuesta válida. –

+0

¡Bien hecho, no pensé que fuera posible! –

+0

¿Qué pasa si un const_cast es un búfer char? Entonces creo que la plantilla Foo (const char (&) [N]) {BARK; } se utilizará. –

Cuestiones relacionadas