С++: конструктор, принимающий только строковый литерал
Можно ли создать конструктор (или подпись функции, если на то пошло), который принимает только строковый литерал, но не является, например, char const *
?
Возможно ли иметь две перегрузки, которые могут различать строковые литералы и char const *
?
С++ 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; }
};
Это не мешает кому-либо злоупотреблять им (я должен найти лучшее имя...), но он позволяет выполнить требуемую оптимизацию, но по-прежнему остается в безопасности.
Ответы
Ответ 1
Нет, вы просто не можете этого сделать - строковые литералы и const char * являются взаимозаменяемыми. Одним из способов может стать введение специального класса для хранения указателей на строковые литералы и создание конструктора, только принимающего это. Таким образом, всякий раз, когда вам нужно передать литерал, вы вызываете конструктор этого класса и передаете временный объект. Это не полностью предотвращает неправильное использование, но делает код намного более удобным.
Ответ 2
Рабочее решение на основе 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
}
Ответ 3
Да, это может сделать! Я придумал решение, которое работает с С++ 03 и без класса-оболочки (что нарушает некоторые неявные преобразования в операторах return).
Прежде всего вам нужен шаблон конструктора для типов const char (&)[N]
, так как это оригинальный тип строковых литералов. Затем вам понадобится еще один для типов char (&)[N]
- например, буферов char, например, чтобы они не попали в конструктор для литералов. И, вероятно, вам также нужен обычный конструктор для типа 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;
}
Ответ 4
Если вы точно знаете, как ваш компилятор и платформа имеют дело со строковыми литералами, возможно, будет возможно написать решение, которое может это сделать. Если вы знаете, что ваш компилятор всегда помещает строковые литералы в определенный регион памяти, вы можете проверить указатель на границы этой памяти. Если он попадает в этот блок, у вас есть строковый литерал; в противном случае у вас есть строка, хранящаяся в куче или стеке.
Однако это решение будет специфичным для платформы/компилятора. Это не будет переносимым.
Ответ 5
На некоторых платформах мне пришлось объявлять строковые литералы как static const char *
, чтобы программа могла получить доступ к тексту из памяти только для чтения. Когда объявлено как const char *
, листинг сборки показал, что текст был скопирован с ПЗУ на переменную стека.
Вместо того, чтобы беспокоиться о приемнике, попробуйте объявить строковые литералы с помощью static const char *
.
Ответ 6
С новыми пользовательскими литералами в С++ 14 (как и для Clang 3.5 - он работает с С++ 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
к каждому литералу, но это не очень важно.