4

Let's say I have this class:

template<int N>
class A {
    public:
    A(const char *s) ...
    private:
    const char buf[N];
};

The template is there so that I can configure the array size without dynamic memory allocation (a requirement). The buf member is const because it is intended to remain constant over the lifetime of the object after the object has been initialized.

To clarify, I also do not have access to the STL.

What are my options to define this constructor so that I can copy the contents of s into buf? One option is const_cast but I'm looking for alternatives that does not require this.

  • 2
    Can you change `char buf[N]` to `std::array` ? – Jarod42 Apr 12 '16 at 07:58
  • @Jarod42 No, unfortunately. I do not have access to STL. –  Apr 12 '16 at 08:00
  • @Ana can you copy the sources of `libstdc++` or something? Even if you don't have access to `std::array`, you will reimplement it right now. Probably worse than in the original implementation. – Bartek Banachewicz Apr 12 '16 at 08:02
  • 3
    The only way to initialize a constant member is through a constructor initializer list. And the only way to initialize an array is to copy into it. Unfortunately you can't copy into an array in an initializer list, which means you can't do what you want. What is it you want to accomplish? What is the class `A` used for? What is the *actual* problem you want to solve? Why does `buf` have to be constant, can't you have the instance of `A` be constant instead? – Some programmer dude Apr 12 '16 at 08:02
  • Can you use a `MyArray` instead as `std::array` is not available ? – Jarod42 Apr 12 '16 at 08:05
  • @JoachimPileborg I'm looking for ways to create a minimal, custom string class for a low-resource micro controller system. `buf` doesn't have to be constant since I can internally ensure it is not modified but I wanted to know if I can flag it as constant as that is what it is intended to be. –  Apr 12 '16 at 08:05
  • BTW, what's happen if input string is bigger/shorter than `buf` ? – Jarod42 Apr 12 '16 at 08:07
  • @Jarod42 that's when your coffee machine takes over your life. – Bartek Banachewicz Apr 12 '16 at 08:14
  • By far the best solution here is to stop using `const` on member variables. – M.M Apr 12 '16 at 08:30
  • `const_cast` is not an option (writing to const object causes undefined behaviour) – M.M Apr 12 '16 at 08:31
  • @M.M Sounds like just removing `const` is the best solution ... –  Apr 12 '16 at 08:34

2 Answers2

4

The solution provided by @Richard Hodges requires the class be initialized with char array, as opposed to char const*, which changes the signature of the constructor. If that doesn't work for your case, then here is one solution which does not change the signature:

template<int N>
class A 
{
       template<size_t...Is>
       A(const char * s, std::index_sequence<Is...>)
        : _assert(std::strlen(s) <= N, "size of buffer is bigger than N"), 
          buf{ (Is, *s != '\0'? *s++: *s)... }
        {}

    public:
        A(const char *s) : A(s, std::make_index_sequence<N>()) {}

    private:
       throwable  _assert;
       const char  buf[N];
};

where throwable is defined as:

 struct throwable
 {
    throwable(bool b, char const * message){
      if (not b) 
          throw std::invalid_argument(message);
    }
 };

The use of throwable ensures that buf doesn't get initialized with buffer larger than N bytes. If however your situation ensures that and thus doesn't need this check, you could remove it. The code should work without it as well, though I'd suggest you to keep it at least in debug mode.

Note that the addition of _assert as member increases the size of the class at least by one byte. To avoid this, we could use empty base class optimiation and do this instead:

template<int N>
class A : private throwable
{
       template<size_t...Is>
       A(const char * s, std::index_sequence<Is...>)
        : throwable(std::strlen(s) <= N, "size of buffer is bigger than N"), 
          buf{ (Is, *s != '\0'? *s++: *s)... }
        {}
    public:
        A(const char *s) : A(s, std::make_index_sequence<N>()) {}

    private:
       const char  buf[N];
};

That saves us 1 byte per instance.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • @M.M: equivalents of `std::make_index_sequence` and `std::index_sequence` are implementable in C++11 as well. – Nawaz Apr 12 '16 at 08:34
  • 1
    I like the throwable, if you give it a constexpr constructor (and the same with A), then you can declare the variable constexpr - and this will give you a compile-time exception with stack trace if the string length is wrong. – Richard Hodges Apr 12 '16 at 08:35
  • 1
    @M.M who wouldn't use c++14 unless they worked for a retarded organisation? – Richard Hodges Apr 12 '16 at 08:36
  • 1
    @RichardHodges ridiculous comment – M.M Apr 12 '16 at 08:40
  • Maybe I'm missing something, but doesn't this index `s` out-of-bounds if `s` is shorter than `N` ? – Quentin Apr 12 '16 at 08:41
  • @Nawaz this takes care of the case where `s` is longer and would be truncated, but what about the opposite ? When `s` isn't quite long enough to fill the buffer ? – Quentin Apr 12 '16 at 09:06
  • @Quentin. Then that part of buffer is unused/unusable. – Captain Giraffe Apr 12 '16 at 09:09
  • @CaptainGiraffe still `s[Is]...` initializes it with values from past the end of `s`. – Quentin Apr 12 '16 at 09:12
  • @Quentin I'm uncertain about the initialization part. Reasonably sure it will default initialize. Are you stating that as a fact or question? – Captain Giraffe Apr 12 '16 at 09:14
  • @Nawaz I'm worrying about the opposite case : when the passed-in buffer is smaller that `buf`. You still access it all the way to its `N-1`th index, which is out-of-bounds. – Quentin Apr 12 '16 at 09:22
  • 1
    @Quentin: Got it. That is really a good catch. Thanks. See the fix. – Nawaz Apr 12 '16 at 09:27
  • 1
    That's a nice trick. And I'm not going insane, that's good too :p – Quentin Apr 12 '16 at 09:31
3

You use an index_sequence and template expansion.

#include <utility>
#include <cstdlib>


template<int N>
class A {

  template<size_t...Is>
    A(const char (&s)[N], std::index_sequence<Is...>)
    : buf{ s[Is]... }
  {}


    public:
    A(const char (&s)[N]) 
      : A(s, std::make_index_sequence<N>())
    {
    }

    private:
    const char buf[N];
};


int main()
{
  A<3> a("ab");

};

And because const char[] is a literal type, it also allows the class to be constexpr:

#include <utility>
#include <cstdlib>


template<int N>
class A {

  template<size_t...Is>
    constexpr A(const char (&s)[N], std::index_sequence<Is...>)
    : buf{ s[Is]... }
  {}


    public:
    constexpr A(const char (&s)[N]) 
      : A(s, std::make_index_sequence<N>())
    {
    }

    private:
    const char buf[N];
};


int main()
{
  constexpr A<3> a("ab");

};

but that changes the signature...

ok then, this:

#include <utility>
#include <cstdlib>
#include <cstring>


template<int N>
class A {

  template<size_t...Is>
    constexpr A(const char *s, size_t len, std::index_sequence<Is...>)
    : buf{ (Is <= len ? s[Is] : char(0))... }
  {}


    public:
    constexpr A(const char *s) 
      : A(s, strlen(s), std::make_index_sequence<N>())
    {
    }

    private:
    const char buf[N];
};


int main()
{
  constexpr A<10> a("ab");

};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 2
    Note that you change signature to take exactly `N` char. – Jarod42 Apr 12 '16 at 08:06
  • @RichardHodges: I've solved that :P (see my solution) – Nawaz Apr 12 '16 at 08:23
  • @Nawaz depends what N represents I guess - length or limit. Who knows, it wasn't specified by the OP. I have a class like this in my own library called value::immutable::string_type. I actually deduce N via a free template function. This allows compile-time string manipulation. – Richard Hodges Apr 12 '16 at 08:27
  • BTW, if you support truncation for `char const*`, you could support it for `char const[]` as well, for that you need to deduce the size. – Nawaz Apr 12 '16 at 08:28