I have a fixed sized string class defined as follows:
template <typename const std::size_t max_size>
class fixed_string {
...
};
This class keeps a char buffer to hold max_size of characters.
I would like the ability to pass objects of this class to methods that take a template instantiation which has a lower value for max_size template parameter without any copying. So for example following method:
void foo(const fixed_string<50>& val) {
}
should be callable using a fixed_string<100>, so that following should work:
void bar() {
fixed_string<100> s = "gg";
foo(s);
}
How could I do this?
Maybe you can add a copy constructor to your class, like this:
template <int max_size>
struct fixed_string {
template <int sz> fixed_string(const fixed_string<sz> &f) {}
};
Then you can pass fixed_string<100>
to function getting const fixed_string<50>&
, but:
fixed_string<50>
. This may be not too bad once you do not copy the data. So in your class you can have, for instance, a static buffer and a reference to a buffer. By default the reference points to internal buffer, but can also reference a buffer of other instance.enable_if
to make sure the size is appropriateThe first instinct would be to reinterpret_cast
from larger to smaller fixed_string
s assuming the compiler lays them out in a similar fashion, but this would be illegal due to the strict-aliasing rules of C++ (Objects may never be accessed by references or pointers of different types, except if one of them is [signed|unsigned] char
)
You can solve this by creating a custom reference template which fixed_string
s can be converted to implicitly:
#include <type_traits>
template<std::size_t N>
struct fixed_string_ref {
char *string;
};
template<std::size_t N>
struct fixed_string_const_ref {
const char *string;
};
template<std::size_t N>
struct fixed_string {
char string[N];
template<std::size_t M, typename std::enable_if<(M < N), int>::type = 0>
operator fixed_string_const_ref<M>() const {
return fixed_string_const_ref<M> { string };
}
template<std::size_t M, typename std::enable_if<(M < N), int>::type = 0>
operator fixed_string_ref<M>() {
return fixed_string_ref<M> { string };
}
};
void foo(fixed_string_const_ref<10>) {}
int main() {
fixed_string<20> f;
foo(f);
//fixed_string<5> g;
//foo(g); <- cannot be converted to fixed_string_const_ref<10>
}
fixed_string[_const]_ref
simply holds a pointer to the fixed_string
's data and can therefore be passed by value without copying the string. There need to be two such types, one const and one non-const, to retain const-correctness.
The enable_if
part ensures that only references to smaller-or-equal fixed_string
s can be created.
Optional: By adding two constructors to fixed_string_const_ref
, ordinary string literals may also be passed as a fixed string reference.
template<std::size_t N>
struct fixed_string_const_ref {
const char *string;
explicit fixed_string_const_ref(const char *s)
: string(s) {}
template<std::size_t M, typename std::enable_if<(M >= N), int>::type = 0>
fixed_string_const_ref(const char (&s)[M])
: string(s) {}
};
// ...
int main() {
// ....
foo("Hello World");
//foo("Hello"); <- cannot be converted to fixed_string_const_ref<10>
}
By making the first one explicit
and the second one restricted via enable_if
, references can again only be created from string literals that are long enough.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With