Bitarr class optimization C++
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.