Ограничить передаваемый параметр строковым литералом - PullRequest
5 голосов
/ 30 сентября 2011

У меня есть класс для переноса строковых литералов и вычисления размера во время компиляции.

Конструктор выглядит следующим образом:

template< std::size_t N >
Literal( const char (&literal)[N] );

// used like this
Literal greet( "Hello World!" );
printf( "%s, length: %d", greet.c_str(), greet.size() );

Однако проблема с кодом есть.Следующий код компилируется, и я хотел бы сделать это ошибкой.

char broke[] = { 'a', 'b', 'c' };
Literal l( broke );

Есть ли способ ограничить конструктор так, чтобы он принимал только строковые литералы c?Обнаружение времени компиляции является предпочтительным, но время выполнения приемлемо, если нет лучшего способа.

Ответы [ 6 ]

8 голосов
/ 30 сентября 2011

Существует способ заставить строковый литерал аргумент: сделать пользовательский литеральный оператор. Вы можете сделать оператор constexpr, чтобы получить размер во время компиляции:

constexpr Literal operator "" _suffix(char const* str, size_t len) {
    return Literal(chars, len);
}

Я не знаю ни одного компилятора, который реализует эту функцию в настоящее время.

4 голосов
/ 30 сентября 2011

Да . Вы можете сгенерировать ошибку времени компиляции со следующим препроцессором:

#define IS_STRING_LITERAL(X) "" X ""

Если вы попытаетесь передать что-либо, кроме строкового литерала, компиляция не удастся. Использование:

Literal greet(IS_STRING_LITERAL("Hello World!"));  // ok
Literal greet(IS_STRING_LITERAL(broke)); // error
3 голосов
/ 30 марта 2014

С компилятором C ++ 11 с полной поддержкой constexpr мы можем использовать конструктор constexpr с использованием функции constexpr, которая компилируется в неконстантное тело выражения в случае, если предусловие завершающего нулевого символа не является выполнено, что привело к сбою компиляции с ошибкой. Следующий код расширяет код UncleBens и вдохновлен статьей блога Andrzej's C ++ :

#include <cstdlib>

class Literal
{
  public:

    template <std::size_t N> constexpr
    Literal(const char (&str)[N])
    : mStr(str),
      mLength(checkForTrailingZeroAndGetLength(str[N - 1], N))
    {
    }

    template <std::size_t N> Literal(char (&str)[N]) = delete;

  private:
    const char* mStr;
    std::size_t mLength;

    struct Not_a_CString_Exception{};

    constexpr static
    std::size_t checkForTrailingZeroAndGetLength(char ch, std::size_t sz)
    {
      return (ch) ? throw Not_a_CString_Exception() : (sz - 1);
    }
};

constexpr char broke[] = { 'a', 'b', 'c' };

//constexpr Literal lit = (broke); // causes compile time error
constexpr Literal bla = "bla"; // constructed at compile time

Я тестировал этот код с помощью gcc 4.8.2. Сбой компиляции с MS Visual C ++ 2013 CTP, так как он все еще не полностью поддерживает constexpr (constexpr функции-члены по-прежнему не поддерживаются).

Вероятно, я должен упомянуть, что мой первый (и предпочтительный) подход состоял в том, чтобы просто вставить

static_assert(str[N - 1] == '\0', "Not a C string.")

в теле конструктора. Сбой при ошибке компиляции, и кажется, что конструкторы constexpr должны иметь пустое тело. Я не знаю, является ли это ограничением C ++ 11 и может ли оно быть смягчено будущими стандартами.

2 голосов
/ 30 сентября 2011

Нет, нет способа сделать это.Строковые литералы имеют определенный тип, и все методы разрешения перегрузки выполняются для этого типа, а не для строкового литерала.Любой метод, который принимает строковый литерал, в конечном итоге принимает любое значение того же типа.

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

0 голосов
/ 16 августа 2017

Однажды я придумал версию C ++ 98, в которой используется подход, аналогичный предложенному @ k.st. Я добавлю это для полноты картины, чтобы рассмотреть некоторые критические замечания относительно макроса C ++ 98. Эта версия пытается обеспечить хорошее поведение , предотвращая непосредственное конструирование через частный ctor и перемещая единственную доступную фабричную функцию в детальное пространство имен, которое в свою очередь используется «официальным» макросом создания. Не совсем красиво, но немного дурак. Таким образом, пользователи должны по крайней мере явно использовать функциональность, которая явно помечена как внутренняя, если они хотят неправильно себя вести. Как всегда, нет способа защитить себя от преднамеренного злого умысла.

class StringLiteral
{
private:
    // Direct usage is forbidden. Use STRING_LITERAL() macro instead.
    friend StringLiteral detail::CreateStringLiteral(const char* str);
    explicit StringLiteral(const char* str) : m_string(str)
    {}

public:
    operator const char*() const { return m_string; }

private:
    const char* m_string;
};

namespace detail {

StringLiteral CreateStringLiteral(const char* str)
{
    return StringLiteral(str);
}

} // namespace detail

#define STRING_LITERAL_INTERNAL(a, b) detail::CreateStringLiteral(a##b)

/**
*   \brief The only way to create a \ref StringLiteral "StringLiteral" object.
*   This will not compile if used with anything that is not a string literal.
*/
#define STRING_LITERAL(str) STRING_LITERAL_INTERNAL(str, "")
0 голосов
/ 30 сентября 2011

Строковый литерал не имеет отдельного типа, чтобы отличать его от массива const char.

Однако это немного усложнит случайную передачу (неконстантных) массивов char.

#include <cstdlib>

struct Literal
{
    template< std::size_t N >
    Literal( const char (&literal)[N] ){}

    template< std::size_t N >
    Literal( char (&literal)[N] ) = delete;
};

int main()
{
    Literal greet( "Hello World!" );
    char a[] = "Hello world";
    Literal broke(a); //fails
}

Что касается проверки во время выполнения, единственная проблема с не-литералом состоит в том, что он может не заканчиваться нулем?Поскольку вы знаете размер массива, вы можете зациклить его (предпочтительно в обратном направлении), чтобы увидеть, есть ли в нем \0.

...