Подтвердить что ты не робот

С++: конструктор, принимающий только строковый литерал

Можно ли создать конструктор (или подпись функции, если на то пошло), который принимает только строковый литерал, но не является, например, 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; }
};

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

4b9b3361

Ответ 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 к каждому литералу, но это не очень важно.