1

I have to deal with char arrays which might be unsigned (because they come from a SCSI data block). I wanted to handle them with this function:

template <typename CharT, size_t Len>
std::string strFromArray(CharT (&src) [Len])
{     
    return std::string((typename boost::make_signed<CharT>::type *) src, Len);
}

The error is in the std::string constructor call, it cannot take signed char/unsigned char but will only take char.

I could of course replace the cast with (char *) src, but then I would lose all compiler errors if I pass in a non-char type.

How can I write this so that it constructs strings out of all "charry" types?

Felix Dombek
  • 13,664
  • 17
  • 79
  • 131

3 Answers3

2

Assuming that the conversion to string indicates that the byte arrays carry null-terminated C strings (e.g. string literals):

#include <string>
#include <stddef.h>

namespace my {
    using std::string;
    using Size = ptrdiff_t;

    namespace detail {
        auto is_char_type( char const* ) -> bool;
        auto is_char_type( signed char const* ) -> bool;
        auto is_char_type( unsigned char const* ) -> bool;
    }  // namespace detail

    template< class Char, Size n >
    auto string_from( Char (&src) [n] )
        -> string
    {     
        (void) sizeof( detail::is_char_type( src ) );
        return string( src, src + n - 1 );
    }
}  // namespace my

auto main() -> int
{
    unsigned char const data[] = "Blah";
    auto const s = my::string_from( data );

#ifdef TEST_WCHAR
    wchar_t const wdata[] = L"Blah";
    auto const ungood_s = my::string_from( wdata );      // Doesn't compile.
#endif
}

If instead of strings this is about arbitrary binary data, then use just src + n instead of src + n - 1.

However, in the case of binary data, there is a possibility that you need a separate length, i.e. not using the length of the raw array itself.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • I don't know whether OP wanted it to work or to fail with `signed char`; I think it's worth mentioning how this code works with that. – anatolyg Nov 03 '15 at 20:19
  • Also, will it work with non-const `char data[100] = "blah"`? Will it work in a surprising way? – anatolyg Nov 03 '15 at 20:20
  • The input sequence isn't compatible but the iterator over the sequence is. I didn't know that. Thanks. (I would like it to work with `signed char`, and it does) – Felix Dombek Nov 03 '15 at 20:20
  • Just the `- 1` for the end iterator doesn't seem to be correct. – Felix Dombek Nov 03 '15 at 20:48
  • @FelixDombek: The end iterators are correct. Arrays of `char`, as C strings, are nullterminated. You need to strip off that null at the end. In passing, I personally only downvote things I am sure of. I do not ever downvote on a vague hunch. Also, I would not downvote even if the end iterators *were* incorrect, but rather post a comment. As I did alain's answer here, which does give you incorrect string lenghts. It's the difference between error of thinking or fact, and simple typo or forgetto. – Cheers and hth. - Alf Nov 03 '15 at 21:17
  • @Cheersandhth.-Alf When you say "The end iterators are correct", you probably assume that OP wants to use null-terminated strings that are common in C. But it looks quite the opposite: OP wants to use `signed char` and `unsigned char` (not `char`) arrays that "come from a SCSI data block". I have no idea why using a static array size is acceptable, but OP used it, and didn't subtract 1 from it. Was this a bug? No idea. – anatolyg Nov 04 '15 at 00:11
  • Also, what does the way you vote have to do with anything? Did I miss some comments that explained that, together with something actually important, and were deleted? – anatolyg Nov 04 '15 at 00:14
  • @anatolyg: Yes, there is an assumption of string since the conversion is to `string`. However, you're right that people have used `string` to carry arbitrary data, and that includes Google. Re the advice about voting, I apparently mistakenly read the "- 1" as a downvote, when it apparently is a quote of the code. At least I can't see any downvote now. Thanks for the clarification about string assumption! Adding that to answer. – Cheers and hth. - Alf Nov 04 '15 at 02:43
  • @anatolyg For example, to get the manufacturer and model info out of a tape drive, there is a fixed length non-terminated string in the ["INQUIRY" structure, p. 38](https://docs.oracle.com/cd/E21419_04/en/LTO5_Vol3_E5b/LTO5_Vol3_E5b.pdf). (padded with spaces, I just left the `boost::trim` out as it's not relevant to the question.) Alf I voted you up, in fact! – Felix Dombek Nov 04 '15 at 18:20
2

I would keep it as simple as possible, and use:

#include <iostream>
#include <string>

namespace conv {
    template <size_t len>
    std::string strFromArray(const char(&arr)[len])
    {
        static_assert(len > 0, "don't use zero-sized arrays");
        return std::string(arr, len - 1);
    }

    template <size_t len>
    std::string strFromArray(const unsigned char(&arr)[len])
    {
        static_assert(len > 0, "don't use zero-sized arrays");
        return std::string((const char *)arr, len - 1);
    }
}

int main()
{
    const char charstr[] = "abcd";
    std::string str = conv::strFromArray(charstr);
    std::cout << str << std::endl;

    const unsigned char ucharstr[] = "efg";
    str = conv::strFromArray(ucharstr);
    std::cout << str << std::endl;

/*not possible:
    const wchar_t wcharstr[] = L"hijk";
    str = conv::strFromArray(wcharstr);
*/

}

Test it live

alain
  • 11,939
  • 2
  • 31
  • 51
  • Beware of the string lengths! (E.g. add some `assert`ing to see the problem.) But good idea about keeping it simple. Wish I had thought of that. ;-) – Cheers and hth. - Alf Nov 03 '15 at 20:58
  • `signed char` is another possibility – M.M Nov 03 '15 at 20:58
  • @Cheersandhth.-Alf, yes, I see what you mean. Your answer does it correctly. I'll change it. – alain Nov 03 '15 at 21:06
  • @M.M I have to confess I never heard of or used `signed char`. But my guess is that it's about being *always* signed, so I would assume a project uses either `signed char` or `char`, but not both? – alain Nov 03 '15 at 21:10
  • sometimes `signed char` is used for a situation where it is important that the variable be signed. Plain `char` may be either signed or unsigned at the implementation's discretion. Maybe OP never uses `signed char` though, so he could do without a case for it. – M.M Nov 03 '15 at 21:15
  • Note: It is not possible to pass zero sized arrays –  Nov 04 '15 at 19:20
  • @DieterLücking, yes, the function template does not work with zero-sized arrays. I don't understand why and asked [this question](http://stackoverflow.com/questions/33510098/zero-sized-arrays-dont-work-with-templates) about it, but got no answer so far. – alain Nov 04 '15 at 19:33
2

Just place an static assert into your function and modify it slightly:

#include <string>

template <typename CharT, std::size_t Len>
std::string strFromArray(CharT (&src) [Len])
{
    // Anythig which looks like a char is considered a char.
    static_assert(sizeof(CharT) == sizeof(char), "Invalid Character Type");
    std::size_t n = Len;
    // Do not use a terminating zero (might be wrong if the source is no char 
    // literal, but an array of binary data.
    if( ! src[n-1])
        --n;
    return std::string(src, src + n);
}

int main()
{
    char c[3] = {};
    signed char sc[3] = {};
    unsigned char uc[3] = {};
    wchar_t wc[3] = {};
    strFromArray(c);
    strFromArray(sc);
    strFromArray(uc);
    // error: static assertion failed: Invalid Character Type
    // strFromArray(wc);
}