C ++: конструктор принимает только строковый литерал - PullRequest
20 голосов
/ 11 января 2010

Можно ли создать конструктор (или, если уж на то пошло, сигнатуру функции), который только принимает строковый литерал, но не, например, char const *

Возможно ли иметь две перегрузки, которые могут различать строковые литералы и 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; }
};

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

Ответы [ 6 ]

19 голосов
/ 11 января 2010

Рабочее решение на основе 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
}
18 голосов
/ 19 июня 2014

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

Прежде всего, вам нужен шаблон конструктора для типов 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;
}
9 голосов
/ 11 января 2010

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

3 голосов
/ 11 января 2010

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

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

0 голосов
/ 13 июня 2014

С новыми пользовательскими литералами в 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 к каждому литералу - но на самом деле это не имеет большого значения.

0 голосов
/ 11 января 2010

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

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

...