Consider using the platform's native word size for arithmetic - GCC has __builtin_*_overflow() functions to help with this, as do many other platforms. Fall back to the usual overflow computation for unknown targets.
Consider using the platform's native word size for arithmetic - GCC has __builtin_*_overflow() functions to help with this, as do many other platforms. Fall back to the usual overflow computation for unknown targets.
Interface
I think the constructor that can be invoked with a std::string should be explicit. We should have implicit conversion only from integer types. And consider accepting std::string_view to avoid converting other string-like types to std::string.
The only virtual function is the destructor. That's a likely sign that we're not intending to derive from this class and own it as a pointer-to-base, so we can make it non-virtual and eliminate the need for a vtable.
operator!= can be defaulted.
Function names is() and bin_func() are not self-explanatory - probably need comments.
We don't need the member versions of binary operations, given the friend functions that implement them. Just pass the first argument by value.
Is operator~() really useful with an arbitrary-width type?
Consider naming the square-root function isqrt() - that's more explicit that we find only the integer part, and is a commonly-used name for this.
Consider providing character stream << and >> operators, rather than requiring construction of std::string objects for this.
to_string() could be a single function with default base.
Do we really need get_bitvector? If we eliminate this, we would then be free to change the internal representation.
Implementation
It's hard to review the implementation without the bitvector interface.
There's a lot more blank lines than I would use - it almost looks double-spaced.
The member swap() exactly duplicates std::swap. It would be better implemented as std::swap(left.bitvec, right.bitvec) - or perhaps bitvector provides a better swap() that could be used instead?
Binary operators implemented in terms of assignment operators can be simplified, by passing first argument by value. That's done correctly in the friend functions, but not in the member versions which I've already said should be removed.
It's not clear why +, ++, *, etc. require trim() to be called - isn't that necessary only for functions which might reduce the length?
Division by zero gives results that are indistinguishable from division of zero. We need a better way to signal this error.
sqrt() implementation looks strange - unless bitvector::lead_bit() does something surprising. I'd like to see the tests for this one.
root() should probably just accept a std::size_t, rather than misleading the user. The values ONE and TWO should probably be const (and lower-case, since we normally use all-caps to draw attention to macro expansions).
to_string could do the error return first, and remove an indentation for most of the function. We should be inserting , (or .) only if the locale specifies a thousands separator. What's the return type of bitvector::at_word()? It's not clear that streaming from this will emit a single digit. In particular, how does stream << r._bitvec.at_word(0); work for both binary and decimal? That's another case where seeing the unit tests would be helpful.
div_rem() is expensive for the exact power-of-two bases - but sufficiently general that we can support more than just the specified set. I think we should use >>= for bases 2, 8 and 16.
set_numeric_value() passes plain char to std::isspace() - that's UB for negative values. Octal and hex input could use <<= instead of *= for better performance.
Unit tests
... are completely absent. I can't approve this code without them.