Initialize union array

I keep getting errors on post. I will build the question one section at a time doing edits after adding each section. If you read this, please be patient.

Windows 11, Visual Studio 2019, C++
Here is the union declaration and declaration in an include file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
union ripemd_union
{
   uint8_t       r_int[max_in];
   char          r_char[max_in];
};

ripemd_union ripemd_input[rm_count ][ max_in ] =
{
   { "" },
   { "a" },
   { "abc" },
   { "message digest" },
   { "abcdefghijklmnopqrstuvwxyz" },
   { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" },
   { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" },
   { "12345678901234567890123456789012345678901234567890123456789012345678901234567890" }
};

I expected the code to initialize it would be:
 
ripemd_union ripemd_input.r_char[rm_count ][ max_in ] = …


But VS sqiggles the “.” This is puzzling. The VS build process does not flag anything in the include file. Does the compiler figure it out, or is there something I am missing?

In the code is:
1
2
      size_t input_length = strlen( ripemd_input.r_char[i]);
      size_t input_length = strlen( ripemd_input->r_char[i]);

VS squiggles the variable name ripemd_input in both. The build error is:

1> E:\WX\ripemd_160_11\ripemd_160_11.cpp(31,51): error C2228: left of '.r_char' must have class/struct/union


To the best of my knowledge, that is exactly what is to the left of ‘.r_char’
What have I missed?

Thank you for your time and patience.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <string>
#include <cstdint>
#include <variant> // https://en.cppreference.com/w/cpp/utility/variant
#include <iomanip>

struct ripemd_union : std::variant< std::uint8_t, std::string >
{
    using base = std::variant< std::uint8_t, std::string > ;
    
    ripemd_union( int n = 0 ) : base( std::uint8_t(n) ) {}
    ripemd_union( const std::string& str ) : base(str) {}
    ripemd_union( const char* cstr ) : base(cstr) {}

    bool is_uint() const noexcept { return index() == 0 ; }
    bool is_str() const noexcept { return index() == 1 ; }

    std::uint8_t uint8() const { return std::get<std::uint8_t>(*this) ; }
    const std::string& str() const { return std::get<std::string>(*this) ; }

    friend std::ostream& operator<< ( std::ostream& stm, const ripemd_union& ru )
    {
        if( ru.is_uint() ) return stm << int( ru.uint8() ) ;
        else return stm << std::quoted( ru.str() ) ;
    }
};

int main()
{
    ripemd_union ripemd_input[] =
    {
        ripemd_union( "" ), 123, "a", 88, "abc", "message digest", 'a', "abcdefghijklmnopqrstuvwxyz"
    };

    for( const ripemd_union& ru : ripemd_input ) std::cout << ru << '\n' ;
}

http://coliru.stacked-crooked.com/a/d46cecb671f28fe4
Well, wow I think. But I have a problem. I have not been able to figure out how templates work. I don’t know what it is but they cause me difficulties. That leads to two requests.
First: Where is a good place I can read about using templates? Not a formal declaration or formal description, a simple guide to read and study and practice a bit.
Second: Just so I’ll know, how can the original code be fixed?
Thank you for that incredible response. I will be implementing it and trying to understand how it works.
read all of chapter 19 here: https://www.learncpp.com/cpp-tutorial/template-classes/
Templates are one of the most aggravating / complicated syntax items in c++. I struggle with them as well, because I do not need them often outside of the provided stl ones.


unions are a C construct, and if you are not using the semistandard but illegal 'union hack' (where you can use any field of the union at any time, the c++ strict rules are that you can only ever use ONE of them per variable) their usefulness is greatly reduced.

here, I don't even see you use the unsigned char. I would just do this:

struct ripemd_union
{
char r_char[max_in];
uint8_t* r_int{(uint8_t*)r_char};
};
this should work in 99% of anything you try to do here, because the auto array to pointer combined with the array notation allowed on pointers lets you say

ripemd_union x;
x.r_int[3]; //ok, pointer using array notation
cout << x.r_char; //ok, c-string override and array to pointer auto conversions
and so on.
the only place it will hiccup is if you had an explicit type problem, where it expected - for example - a fixed sized array in a function but got the pointer (auto pointer back to array isnt a thing). This can be avoided.

its still very C-ish, but its simple and gets the job done if you are willing to work within its limitations. If you need more, you can get it (see the above much more complicated and much more C++ friendly answer) or move on toward templates if you like.

this struct can be initialized via
ripemd_union x[3] {{"abc"},{"def"},{"123456"}};


1
2
3
4
5
6
7
8
all that aside, you can use this, maybe?

	
ripemd_union ripemd_input[rm_count ][ max_in ] =
{
   { .r_char = {""} },
   { .r_char = {"a"} },
...


that maybe only works in C. I don't know right now.
Last edited on
that maybe only works in C


It works in C++20, except I believe you need to add more braces.

1
2
3
4
5
6
7
8
9
10
11
ripemd_union ripemd_input[ rm_count ][ max_in ] =
{
   { { .r_char = "" } },
   { { .r_char = "a" } },
   { { .r_char = "abc" } },
   { { .r_char = "message digest" } },
   { { .r_char = "abcdefghijklmnopqrstuvwxyz" } },
   { { .r_char = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" } },
   { { .r_char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" } }, 
   { { .r_char = "12345678901234567890123456789012345678901234567890123456789012345678901234567890" } }
};
Last edited on
This compiles OK as C++20 (VS):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

constexpr size_t max_in { 100 };
constexpr size_t rm_count { 8 };

union ripemd_union
{
	uint8_t       r_int[max_in] {};
	char          r_char[max_in];
};

const ripemd_union ripemd_input[rm_count]
{
   { .r_char = "" },
   { .r_char = "a" },
   { .r_char = "abc" },
   { .r_char = "message digest" },
   { .r_char = "abcdefghijklmnopqrstuvwxyz" },
   { .r_char = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" },
   { .r_char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" },
   { .r_char = "12345678901234567890123456789012345678901234567890123456789012345678901234567890" }
};

int main() {
	for (size_t i {}; i < rm_count; ++i)
		std::cout << ripemd_input[i].r_char << '\n';
}


which displays:



a
abc
message digest
abcdefghijklmnopqrstuvwxyz
abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
12345678901234567890123456789012345678901234567890123456789012345678901234567890

You can also use a union constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <cstdio>

constexpr size_t max_in { 100 };
constexpr size_t rm_count { 8 };

union ripemd_union
{
	uint8_t       r_int[max_in] {};
	char          r_char[max_in];

	ripemd_union(const char* c) {
		snprintf(r_char, max_in, "%s", c);
	}
};

const ripemd_union ripemd_input[rm_count]
{
   { "" },
   { "a" },
   { "abc" },
   { "message digest" },
   { "abcdefghijklmnopqrstuvwxyz" },
   { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" },
   { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" },
   { "12345678901234567890123456789012345678901234567890123456789012345678901234567890" }
};

int main() {
	for (size_t i {}; i < rm_count; ++i)
		std::cout << ripemd_input[i].r_char << '\n';
}

I did not know unions added a ctor, that is much cleaner!
Why do we need a union for this?
It is inconceivable that std::uint8_t is available, but is not either unsigned char or char.
(Though the standard does allow it to be an implementation-defined extended unsigned integer type, no implementation does that.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <type_traits>
#include <string>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iomanip>

struct ripemd
{
    static constexpr std::size_t MAX_CHARS = 100 ;
    char cstr[MAX_CHARS+1] ;

    ripemd( std::string str )
    {
        str.resize( MAX_CHARS+1, 0 ) ;
        std::copy( str.begin(), str.end(), cstr ) ;
    }

    ripemd( const char* cstr ) : ripemd( std::string( cstr ? cstr : "" ) ) {}

    operator const char* () const noexcept { return cstr ; }
    std::size_t size() const { return std::strlen(cstr) ; }

    // verify that the aliased type std::uint8_t is either unsigned char, or char
    // (the object representation of any object may be examined as a sequence of bytes)
    // see: https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing
    static_assert( std::is_same< std::uint8_t, unsigned char >::value || std::is_same< std::uint8_t, char >::value ) ;
    const std::uint8_t* begin() const noexcept { return reinterpret_cast< const std::uint8_t* >(+cstr) ; }
    const std::uint8_t* end() const { return begin() + size() ; }

    friend std::ostream& operator << ( std::ostream& stm, const ripemd& rmd )
    { return stm << std::quoted(rmd.cstr) ; }

    friend std::ostream& dump( const ripemd& rmd, std::ostream& stm = std::clog )
    {
        stm << "text: " << rmd << "\nbytes:" ;
        for( int v : rmd ) stm << std::setw(4) << v ;
        return stm << "\n\n" ;
    }
};

int main()
{
    const ripemd ripemd_input[] = { "", "a", "abc", "message digest", "abcdefghijklmnopqrstuvwxyz", "0123\t4567\t89\003\004" };
    for( const ripemd& rmd : ripemd_input ) dump(rmd) ;
}

http://coliru.stacked-crooked.com/a/323f66709935d95c
Its best not as a union. These responses were to this ask:
Second: Just so I’ll know, how can the original code be fixed?

so yes, it can be fixed, even if it should be scrapped.
Topic archived. No new replies allowed.