7

In C++1y, can I have a reference class that binds to a string literal but not to char* or char[]& or similar?

class LitRf{
  const char* data_;
  size_t size_;
public:
  LitRf(const char* /*?*/ s) : data_{s},size_{sizeof(s)} { /*?*/ }
};
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • The best I can come up with is to provide a templated overload that static_asserts, or use the explicit keyword. – Borgleader Mar 03 '16 at 12:34
  • @Borgleader But i don't think there is a way to distinguish between a string literal and a const char array. – HolyBlackCat Mar 03 '16 at 12:36
  • @Borgleader How would you `static_assert` that something is a string literal? And how could `explicit` help here? Actually, if you can `static_assert` that something is a string literal, you can also use the same condition with SFINAE to control overload resolution not to bind to the function in the first place. – 5gon12eder Mar 03 '16 at 12:37
  • @Borgleader Please share it. – Petr Skocik Mar 03 '16 at 12:37
  • I guess the best you can do is to use `const char (&s)[N]` (with `template`) as the parameter type. But it also binds to any const char array other than a string literal. – leemes Mar 03 '16 at 12:40
  • @HolyBlackCat [Well it sort of does](http://coliru.stacked-crooked.com/a/e8ea43728651593c), like I said "the best i can come up with" or i should have said closest. I dont think what OP wants/needs is doable 100%. – Borgleader Mar 03 '16 at 12:45
  • Demo code for my last comment: http://ideone.com/d9olzv – leemes Mar 03 '16 at 12:49
  • @Borgleader None of your test calls is called with a string literal. They both use variables, not literals. I understand the question that exactly that is what should be probihited... – leemes Mar 03 '16 at 12:53
  • @leemes your solution also binds to non-const char arrays. – Petr Skocik Mar 03 '16 at 12:56
  • 1
    I know. You cannot change that. Constness can always be added, but not removed. You can only add a non-const overload, but `=delete` it. – leemes Mar 03 '16 at 12:57
  • Did you really mean `sizeof(s)`? Because that's 1 in your case... You probably meant `strlen(s)` or `sizeof(s) - 1` if `s` is an array. Or `sizeof(s)` even in that case? Note the terminating zero, which is included in the array size, but not in the string size (strlen). – leemes Mar 03 '16 at 13:16
  • @leemes `sizeof` is usually 4 (32 bit archs, CHAR_BIT==8) or 8 (64 bit archs) for a pointer, or the storage size (including the terminating 0) for a literal or array. I meant for my `size_` to store the storage size (exactly like in your answer), but subtracting or not subtracting 1 from the size bears little relevance to the question. ;-) – Petr Skocik Mar 03 '16 at 13:40
  • Yeah, I meant 4 of course ;) – leemes Mar 03 '16 at 13:45

3 Answers3

3

C++11 removed the only formal way to detect a string literal as such, namely via its implicit conversion to pointer to non-const.

Anyway using the little trick one had to employ a macro.

In C++11 and later the best you can do is to establish a strong convention that an array of const character type is a literal.

I.e., with your example,

class LitRf
{
private:
    const char* data_;
    Sz size_;

    template< size_t n >
    LitRf( char (&)[n] ) = delete;

public:
    template< size_t n >
    LitRf( char const (&s)[n] )
        : data_{s}, size_{sizeof(s) - 1}
    {}
};

Note the use of size_t instead of a possibly signed type Sz. This ensure that the code will compile with g++. Unfortunately that compiler, or older versions of it, has a bug where it's quite insistent on size_t, or else it refuses to accept the code.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Thanks. `char const []` ~ `string literal` is a reasonable compromise. Unfortunately, this will also initialize from a non-const char array. (Never mind the `Sz`. I didn't know it was anything -- I use it as a `typedef` for `std::size_t` in my namespace) – Petr Skocik Mar 03 '16 at 13:02
  • But you can't differentiate between an array of `const char` and a `const` array of `char` – Barry Mar 03 '16 at 13:06
  • Why `sizeof(s) - 1`? I know, the terminating 0, but OP didn't subtract that originally. – leemes Mar 03 '16 at 13:07
  • Ah, forget it - I guess OP didn't mean to do `sizeof(s)` because that's 1 in his case - he probably meant `strlen` which in turn would indeed match your code. – leemes Mar 03 '16 at 13:14
  • Added an inaccessible and deleted constructor to prevent construction from array of non-`const` `char`. – Cheers and hth. - Alf Mar 03 '16 at 13:19
3

I guess the best you can do is to use const char (&s)[N] (with template<size_t N>) as the parameter type. But it also binds to any const char array other than a string literal.

Add a deleted non-const char array constructor to prohibit calling it with a non-const array.

class LitRf
{
    const char* data_;
    Sz size_;
public:
    template<size_t N>
    LitRf(char const (&s)[N])
        : data_{s}, size_{N}
    {}

    template<size_t N>
    LitRf(char (&s)[N]) = delete;
};

Other than that, you can use a macro wrapper, which (when the constructor is never used without it) makes it only possible to construct an object from a literal, not even via a variable.

#define MakeLitRf(s) LitRf(s "")

The idea is to concatenate two string literals, of which the second one is just an empty string. This is only possible if the first is also a string literal; putting a variable there is a syntax error. After macro expansion, the compiler sees something like LitRf("foo" "") which is equivalent to LitRf("foo"). Some examples:

auto x = MakeLitRf("foo");  // works

const char *foo = "foo";
auto x = MakeLitRf(foo);    // fails

auto x = LitRf(foo);        // works, but we want it to fail...

In the last case, the user unintentionally (or intentionally?) didn't use the macro, making our work worthless. To make it fail too, add a hidden parameter to the constructor, which is required to be added when called directly (and in the macro's definition, of course):

class LitRf
{
    const char* data_;
    Sz size_;
public:
    // Called by the macro MakeLitRf. Unlikely to be called directly unless the user knows what he's doing.
    LitRf(const char *s, void *)
        : data_{s}, size_{N}
    {}

    // Called without macro! Throw a compiler error, explaining what's wrong.
    LitRf(const char *s)
    {
        static_assert(false, "Please use the macro `MakeLitRf` with a string literal to construct a `LitRf`.");
    }
};

#define MakeLitRf(s) LitRf(s "", nullptr)
leemes
  • 44,967
  • 21
  • 135
  • 183
1
class LitRf{
  const char* data_;
  Sz size_;
public:
  LitRf(const char* /*?*/ s) : data_{s},size_{sizeof(s)} { /*?*/ }
  LitRf(char*) = delete;
};
user764486
  • 160
  • 5