Consider the following function:
int libfoo_do_smth(libfoo_context_t* context, const char* x); //returns TRUE on success
Wrapper should be something like
void libfoo::context::do_smth(const char* x) { if(!libfoo_do_smth(m_context, x)) throw libfoo::exception("do_smth failed"); }; template<class CharTraits, class Allocator> void libfoo::context::do_smth(const std::basic_string<char, CharTraits, Allocator>& x) { do_smth(x.data()); };It's OK (except that the second overload is trivial). But what if libfoo_do_smth is taking two string arguments? Or maybe three ones ore more? Even in case of two arguments we need four overloads and we'll confront with a combinatorial explosion of number of trivial overloads increasing number of string arguments. To solve this problem I use a simple class that is a zero-ended string wrapper. It mimics the std::string interface but neither owns the memory used to store array of char nor knows the length of the string it refers to.
template<typename Char> struct basic_const_c_string { template<typename C> basic_const_c_string(const basic_const_c_string<C> string) : m_data{string.data()} {}; basic_const_c_string(const Char* string) : m_data{string} { if(!string) throw std::invalid_argument("null-pointer initialization"); }; template<typename CharTraits, typename Allocator> basic_const_c_string( const std::basic_string< typename std::remove_const<Char>::type, CharTraits, Allocator >& string ) : m_data{string.data()} {}; template<typename C, typename Traits, typename Allocator> basic_const_c_string(std::basic_string<C, Traits, Allocator>&&) = delete; const Char* data() const { return m_data; }; const Char* c_str() const { return data(); }; private: const Char* m_data; }; //basic_const_c_string typedef basic_const_c_string<char> const_c_string; typedef basic_const_c_string<wchar_t> const_c_wstring;Now there's no need in overloads and our wrapper may look like following:
void libfoo::context::do_smth(const_c_string x) { if(!libfoo_do_smth(m_context, x.data())) throw libfoo::exception("do_smth failed"); };Obviously there's no need in overloads in case of any number of string parameters. Now however we cannot pass a temporary std::string parameter to our function. Corresponding constructor of basic_const_c_string is deleted because we cannot move std::string to basic_const_c_string (it doesn't owns memory). To circumvent this I use combination of universal references and overloading:
template<typename Char, typename String> basic_const_c_string<Char> make_const_string(String&& string) { return {string}; }; template<typename Char, typename CharTraits, typename Allocator> const std::basic_string<Char, CharTraits, Allocator> make_const_string(std::basic_string<Char, CharTraits, Allocator>&& string) { return {string}; };So the wrapper code becomes (in case of two string parameters):
template<class String1, class String2> void libfoo::context::do_smth_else(String1&& x, String2&& y) { auto temp_x = make_const_string<char>(std::forward<String1>(x)); auto temp_y = make_const_string<char>(std::forward<String2>(y)); if(!libfoo_do_smth_else(m_context, temp_x.data(), temp_y.data())) throw libfoo::exception("do_smth_else failed"); };Well, now we need one template parameter for one string argument, but it's look better than four trivial overloads of one function.
No comments:
Post a Comment