Herb has a point, in that taking by-value when you already have storage allocated within can be inefficient and cause a needless allocation. But taking by const& is almost as bad, as if you take a raw C string and pass it to the function, a needless allocation occurs.
What you should take is the abstraction of reading from a string, not a string itself, because that is what you need.
Now, you can do this as a template:
class employee {
std::string name_;
public:
template<class T>
void set_name(T&& name) noexcept { name_ = std::forward<T>(name); }
};
which is reasonably efficient. Then add some SFINAE maybe:
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); }
};
so we get errors at the interface and not the implementation.
This isn't always practical, as it requires exposing the implementation publically.
This is where a string_view type class can come in:
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 e-b; }
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 constructed directly from a std::string or a "raw C string" and nearly costlessly store what you need to know in order to produce 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 as now our set_name has a fixed interface (not a perfect forward one), it can have its implementation not visible.
The only inefficiency is that if you pass in a C-style string pointer, you somewhat needlessly go over its size twice (first time looking for the '\0', second time copying them). On the other hand, this gives your target information about how big it has to be, so it can pre-allocate rather than re-allocate.