Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting from one template instantiation to another

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?

like image 599
user3612009 Avatar asked Nov 09 '22 03:11

user3612009


2 Answers

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:

  1. It will create a new instance of 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.
  2. Probably, still need to use enable_if to make sure the size is appropriate
  3. Having implemented the buffer-reference separation, you make make the whole thing more implicit, and create a special type of constructor which will be distinguished from regular copy constructor (in which you, probably, want to accept smaller and not larger buffers)
like image 153
Michael Gopshtein Avatar answered Jan 04 '23 02:01

Michael Gopshtein


The first instinct would be to reinterpret_cast from larger to smaller fixed_strings 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_strings 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_strings 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.

like image 26
Fabian Knorr Avatar answered Jan 04 '23 03:01

Fabian Knorr