11

I have a class to wrap string literals and calculate the size at compile time.

The constructor looks like this:

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

There is problem with the code however. The following code compiles and I would like to make it an error.

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

Is there a way to restrict the constructor so that it only accepts c string literals? Compile time detection is preferred, but runtime is acceptable if there is no better way.

deft_code
  • 57,255
  • 29
  • 141
  • 224
  • @Nawaz the question mentioned specifically C string literals. I thought it was appropriate with that mention. – JaredPar Sep 30 '11 at 16:56
  • The c tag may not be appropriate. We'll see. I'm hoping that someone will have some trick for this using c++11's new features (`constexpr`, variadics, etc). – deft_code Sep 30 '11 at 16:59
  • Would having the size only at runtime be too much of a loss? I'll post an answer nonetheless. – R. Martinho Fernandes Sep 30 '11 at 17:06
  • @JaredPar: C-string literals are not only C-string literals, they're C++-string literals as well. In fact, there are nothing called C-string literals. There are only string literals, which happen to be in both languages! – Nawaz Sep 30 '11 at 17:08
  • @Nawaz I realize c style string literals are available in both languages. Given the C++11 tags and the explicit mention of c style string literals I thought perhaps there was a new feature in C++11 which was different than c style strings (not very up to date with the C++11 feature list) Hence I added the tag. It's since been removed so no worries. – JaredPar Sep 30 '11 at 17:11
  • The type of `"hello"` is `char[6]`, and there's no way you can distinguish that from any other `char[6]`, or from `{'h','e','l','l','o','\0'}` if you will. You can add a static assertion that the last element be zero, if you like. – Kerrek SB Sep 30 '11 at 17:32
  • Linked duplicate: [Verify type of string (e.g. literal, array, pointer) passed to a function](http://stackoverflow.com/q/5819217/514235) – iammilind Jul 06 '16 at 04:31

6 Answers6

11

There is a way to force a string literal argument: make a user defined literal operator. You can make the operator constexpr to get the size at compile time:

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

I don't know of any compiler that implements this feature at this time.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • This operator does not allow for `"Hello world"_literal`, though :( – Johannes Schaub - litb Sep 30 '11 at 17:33
  • @JohannesSchaub-litb oh :( You're right. It is only for integer and floating-point literals. That's a pity. I'm leaving the answer, but it's not as cool now :( – R. Martinho Fernandes Sep 30 '11 at 17:43
  • @Johannes, There is an easier way to do this with current C++ standard. – iammilind Sep 30 '11 at 17:50
  • Can user defined literals be a `constexpr`? If so would the compiler still calculate the size at runtime? Would this be safe? I don't know that the standard guarantees that a string literals memory has static duration, but every compiler implements it this way. I'm worried that the `str` parameter doesn't point to static duration data. – deft_code Sep 30 '11 at 18:11
  • @deft_code you can't make literal operator templates, unless you use the one like I had before. So, even if you had the size computed at compile-time, you'd have to store it and retrieve it at runtime :( – R. Martinho Fernandes Sep 30 '11 at 18:14
  • @R.MartinhoFernandes but at least you have the length now without computing it. That's super cool too :) – Johannes Schaub - litb Sep 30 '11 at 18:29
  • @R.MartinhoFernandes: that's exactly what I'm after. I don't need access to the string size at compile time, I just want to calculate it at compile time. However, If user defined literal are allowed to be `constexpr`s then I could store size as a constexpr and then have access to size at compile time. – deft_code Sep 30 '11 at 19:14
  • 1
    @deft_code UD literal operators are allowed to be `constexpr`. [Here](http://ideone.com/zJuQl)'s how good the GCC support of `constexpr` is (this compiles fine with a snapshot of 4.7), so your problem is essentially solved as soon as UD literals are supported. – Luc Danton Sep 30 '11 at 23:47
  • 2
    to experiment further, here is @Luc's code without macros (which is then not a simulation of the UD literal function anymore though): http://ideone.com/JF4cx – Johannes Schaub - litb Oct 01 '11 at 00:07
  • @Johannes the operator *does* allow for "Hello world"_literal - all sorts of string user-defined literals are allowed including raw strings. I'm hurrying on ud literals. ;-) – emsr Oct 02 '11 at 15:38
  • @emsr: Johannes was commenting on an earlier version of this answer. Given context, he was right. – R. Martinho Fernandes Oct 02 '11 at 16:17
7

Yes. You can generate compile time error with following preprocessor:

#define IS_STRING_LITERAL(X) "" X ""

If you try to pass anything other than a string literal, the compilation will fail. Usage:

Literal greet(IS_STRING_LITERAL("Hello World!"));  // ok
Literal greet(IS_STRING_LITERAL(broke)); // error
iammilind
  • 68,093
  • 33
  • 169
  • 336
3

With a C++11 compiler with full support for constexpr we can use a constexpr constructor using a constexpr function, which compiles to a non-const expression body in case the trailing zero character precondition is not fulfilled, causing the compilation to fail with an error. The following code expands the code of UncleBens and is inspired by an article of Andrzej's C++ blog:

#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

I tested this code with gcc 4.8.2. Compilation with MS Visual C++ 2013 CTP failed, as it still does not fully support constexpr (constexpr member functions still not supported).

Probably I should mention, that my first (and preferred) approach was to simply insert

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

in the constructor body. It failed with a compilation error and it seems, that constexpr constructors must have an empty body. I don't know, if this is a C++11 restriction and if it might be relaxed by future standards.

k.st.
  • 41
  • 4
2

No there is no way to do this. String literals have a particular type and all method overload resolution is done on that type, not that it's a string literal. Any method which accepts a string literal will end up accepting any value which has the same type.

If your function absolutely depends on an item being a string literal to function then you probably need to revisit the function. It's depending on data it can't guarantee.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 6
    @iammilind your method depends on every caller being a good citizen. The entire purpose of the question is to stop people from being bad citizens. – JaredPar Sep 30 '11 at 17:57
0

I once came up with a C++98 version that uses an approach similar to the one proposed by @k.st. I'll add this for the sake of completeness to address some of the critique wrt the C++98 macro. This version tries to enforce good behavior by preventing direct construction via a private ctor and moving the only accessible factory function into a detail namespace which in turn is used by the "offical" creation macro. Not exactly pretty, but a bit more fool proof. This way, users have to at least explicitly use functionality that is obviously marked as internal if they want to misbehave. As always, there is no way to protect against intentional malignity.

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, "")
Nick Nougat
  • 181
  • 5
0

A string literal does not have a separate type to distinguish it from a const char array.

This, however, will make it slightly harder to accidentally pass (non-const) char arrays.

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

As to runtime checking, the only problem with a non-literal is that it may not be null-terminated? As you know the size of the array, you can loop over it (preferable backwards) to see if there's a \0 in it.

UncleBens
  • 40,819
  • 6
  • 57
  • 90
  • Another potential problem is that a literal has static storage duration, but a non-literal could have a shorter duration. If the goal is to avoid unnecessary copying, the parameter might be saved and could become a dangling pointer for non-literals. (Apologies for digging up an old answer.) – JaMiT Jan 01 '21 at 20:49