Herb has a point that accepting a value when you already have dedicated storage can be inefficient and cause unnecessary distribution. But accepting const& almost as bad as if you took a raw C string and passed it to a function, an unnecessary distribution occurs.
What you have to do is an abstraction of reading from a string, not the string itself, because that is what you need.
Now you can do it as template :
class employee { std::string name_; public: template<class T> void set_name(T&& name) noexcept { name_ = std::forward<T>(name); } };
which is quite effective. Then add SFINAE, perhaps:
class employee { std::string name_; public: template<class T> std::enable_if_t<std::is_convertible<T,std::string>::value> set_name(T&& name) noexcept { name_ = std::forward<T>(name); } };
therefore, we get errors in the interface, and not in the implementation.
This is not always practical, as it requires public publication.
There could be a class like string_view :
template<class C> struct string_view { // could be private: C const* b=nullptr; C const* e=nullptr; // key component: C const* begin() const { return b; } C const* end() const { return e; } // extra bonus utility: C const& front() const { return *b; } C const& back() const { return *std::prev(e); } std::size_t size() const { return eb; } bool empty() const { return b==e; } C const& operator[](std::size_t i){return b[i];} // these just work: string_view() = default; string_view(string_view const&)=default; string_view&operator=(string_view const&)=default; // myriad of constructors: string_view(C const* s, C const* f):b(s),e(f) {} // known continuous memory containers: template<std::size_t N> string_view(const C(&arr)[N]):string_view(arr, arr+N){} template<std::size_t N> string_view(std::array<C, N> const& arr):string_view(arr.data(), arr.data()+N){} template<std::size_t N> string_view(std::array<C const, N> const& arr):string_view(arr.data(), arr.data()+N){} template<class... Ts> string_view(std::basic_string<C, Ts...> const& str):string_view(str.data(), str.data()+str.size()){} template<class... Ts> string_view(std::vector<C, Ts...> const& vec):string_view(vec.data(), vec.data()+vec.size()){} string_view(C const* str):string_view(str, str+len(str)) {} private: // helper method: static std::size_t len(C const* str) { std::size_t r = 0; if (!str) return r; while (*str++) { ++r; } return r; } };
such an object can be built directly from std::string or "raw C string" and store what you need to know almost without fail in order to create a new std::string from it.
class employee { std::string name_; public: void set_name(string_view<char> name) noexcept { name_.assign(name.begin(),name.end()); } };
and since now our set_name has a fixed interface (not a perfect forward), it may not implement its implementation.
The only inefficiency is that if you pass a C-style string pointer, you will, of course, go around your size a bit twice (first find '\0' , copy them a second time). On the other hand, it gives your targeted information about how large it is, so it can be distributed in advance, not redistributed.