Bitarr class optimization C++ I have implemented a bitarr class that does bit manipulations. I would love to know if there is any way to make the code more optimized. Any input would be much appreciated. Thank you! Note: int main() should not be modified.
    #include &lt;iostream&gt;    
    #include &lt;vector&gt;    
    #include &lt;climits&gt;
    #include &lt;cstring&gt;

    template&lt;size_t NumBits&gt;
    class bitarr
    {
    private:
        static const unsigned NumBytes = (NumBits + CHAR_BIT - 1) / CHAR_BIT;//find number of bytes to track least memory footprint
        unsigned char arr[NumBytes];
    public:
        bitarr() { std::memset(arr, 0, sizeof(arr)); } //initialize array to 0

        void set(size_t bit, bool val = true) {
            if (val == true)
            {
                arr[bit / CHAR_BIT] |= (val &lt;&lt; bit % CHAR_BIT);//left shift and OR with masked-bit
            }
        }

        bool test(size_t bit) const {
            return arr[bit / CHAR_BIT] &amp; (1U &lt;&lt; bit % CHAR_BIT); //left shift and AND with masked-bit
        } 
        const std::string to_string(char c1, char c2)
        {
            std::string str;
            for (unsigned int i = NumBits; i-- &gt; 0;)
                str.push_back(static_cast&lt;char&gt;
                ('0' + test(i)));
            while (str.find(&quot;0&quot;) != std::string::npos) {
                str.replace(str.find(&quot;0&quot;), 1, std::string{ c1 });
            }
            while (str.find(&quot;1&quot;) != std::string::npos) {
                str.replace(str.find(&quot;1&quot;), 1, std::string{ c2 });
            }
            return str;
        } 
        friend std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const bitarr&amp; b)
        {
            for (unsigned i = NumBits; i-- &gt; 0; ) 
                os &lt;&lt; b.test(i);
            return os &lt;&lt; '\n';
        }
    };

    int main()
    {
        try
        {
            bitarr&lt;5&gt; bitarr;
            bitarr.set(1);
            bitarr.set(2);

            const std::string strr = bitarr.to_string('F', 'T');
            std::cout &lt;&lt; strr &lt;&lt; std::endl;
            if (strr != &quot;FFTTF&quot;)
            {
                throw std::runtime_error{ &quot;Conversion failed&quot; };
            }
        }
        catch (const std::exception&amp; exception)
        {
            std::cout &lt;&lt; &quot;Conversion failed\n&quot;;
        }
    }
Use size_t consistently
You use both size_t and unsigned for counting. Stick with size_t.
Use default member initialization
You can use default member initialization to ensure arr[] is initialized, without having to call memset():
class bitarr
{
    static const unsigned NumBytes = (NumBits + CHAR_BIT - 1) / CHAR_BIT;
    unsigned char arr[NumBytes] = {};
    ...
};

You can then also remove the constructor completely.
Unnecessary if-statement in set()
You don't need to check whether val is true in set(). If it is false, the body of the if-statement will still do the right thing. While it might look like that would do a lot of work for nothing, the processor might easily mispredict this condition, making it less efficient than not having the if at all.
Make all functions that do not modify arr[] const
You made the function test() const, but to_string() also does not modify the bit array, so you can make that function const as well.
Optimize to_string()
Your function to_string() is very inefficient. The caller provides you with the characters to use for the representation of one and zero bits, but you first ignore that and build a string of '0' and '1', and then replace those characters one by one. Why not build the string directly using c1 and c2? Also, since you know how long the string will be, you should reserve space for all the characters up front.
std::string to_string(char c1, char c2) const
{
    std::string str;
    str.reserve(NumBits);

    for (size_t i = NumBits; i-- &gt; 0;)
        str.push_back(test(i) ? c2 : c1);

    return str;
}

You implementation also has bugs: to_string('0', '1') results in an infinite loop, and to_string('1', '0') always results in a string with all zeroes.