2010-01-11 2 views
18

Можно ли создать конструктор (или подпись функции, если на то пошло), что только принимает строковый литерал, но не такой, например. char const *?C++: конструктор, принимающий только строковый литерал

Возможно ли иметь две перегрузки, которые могут различать строковые литералы и char const *?

C++ 0x мог бы сделать это с помощью специального суффикса - но я ищу «более раннее» решение.

Обоснование: избегая кучи копий строк, которые не будут изменены при указании в виде строковых литералов.

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

Примечание: - так как он приходит в ответах: код в вопросе не использует std::string вообще, но хороший пример был бы:

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

Результаты:

(1) не может быть сделано.
(2) Я понял, что даже не ищу литерала, но для константы времени компиляции (т. Е. «Ничего, что не нужно копировать»).

Я, вероятно, использовать следующий шаблон вместо:

const literal str_Ophelia = "Ophelia"; 

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

с простой

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

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

+0

Я не думаю, что это так, так как 'string' имеет конструктор, который принимает a char const *. –

+0

Не могли бы вы рассказать об обосновании? Я не могу понять, почему вы хотели бы скопировать строковый литерал, но НЕ копировать 'const char *' (или наоборот). Вы не можете изменить оба точно таким же образом. – Hexagon

+0

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

ответ

9

Нет, вы просто не можете этого сделать - строковые литералы и const char * являются взаимозаменяемыми. Одним из способов может стать введение специального класса для хранения указателей на строковые литералы и создание конструктора, только принимающего это. Таким образом, всякий раз, когда вам нужно передать литерал, вы вызываете конструктор этого класса и передаете временный объект. Это не полностью предотвращает неправильное использование, но делает код намного более удобным.

+0

Это все еще верно для C++ 14? Хм, может быть, пользовательские литералы могут быть использованы? http://en.cppreference.com/w/cpp/language/user_literal – panzi

3

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

Однако это решение было бы специфичным для платформы/компилятора. Это не было бы портативным.

+0

, вероятно, единственный «безопасный» способ сделать это, но я не хочу полагаться на столь неуправляемый обман. В любом случае, спасибо! – peterchen

19

Рабочий раствор на основе 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: Зачем комментировать? С какими бит вы не согласны? –

+1

Возможно, факт, что он не работает - он распознает массивы символов как строковые литералы для начала. –

+0

тем не менее, идея goo для работы над проблемой разрешения перегрузки. – peterchen

0

На некоторых платформах, я должен был объявить строковые литералы как static const char * для того, чтобы получить доступ к программе текста из Read-Only Memory. Когда объявлено как const char *, сборка показала, что текст был скопирован из ПЗУ на переменную стека.

Вместо того, чтобы беспокоиться о приемнике, попробуйте объявить строковые литералы с помощью static const char *.

0

с новым определяемых пользователем литералов в C++ 14 (как для Clang 3.5 - она ​​работает с C++ 11 тоже), есть элегантное решение:

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

Он может быть использован как это:

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

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

Существует один недостаток: вы должны приложить _L к каждому буквальным - но это не имеет большого значения, на самом деле.

17

Да, это может быть сделано! Я придумал решение, которое работает с C++ 03 и без класса-оболочки (что нарушает некоторые неявные преобразования в операторах return).

Прежде всего, вам нужен шаблон конструктора для типов const char (&)[N], так как это оригинальный тип строковых литералов. Тогда вам также понадобится еще один для типов char (&)[N] - например, буферов символов, чтобы они не попали в конструктор для литералов. И, вероятно, вам также нужен обычный конструктор для типа 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 

Теперь проблема в том, что для строки литералов компилятор до сих пор думает, что const char* конструктор является лучшим выбором, чем экземпляр шаблона, так как массив в-указатель преобразования имеют точного совпадения ранга , (13.3.3.1.1)

Итак, трюк заключается в том, чтобы снизить приоритет конструктора const char*. Это можно сделать, изменив его на шаблон и используя SFINAE, чтобы он соответствовал только типу const char*. Конструктор не имеет возвращаемого значения и только один параметр, необходимый для вывода типа. Поэтому еще один «фиктивный параметр» со значением по умолчанию, необходимо, что использует условный тип признака: template<typename T> Foo(T, typename IsCharPtr<T>::Type=0)

Решение:

#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

Я думаю, что это должно быть принято как действительный ответ. –

+0

Молодец, я не думал, что это возможно! –

+0

Что делать, если один const_cast является буфером символов? Тогда я считаю, что шаблон Foo (const char (&) [N]) {BARK; } будет использован. –