Reputation: 3550
I use std::string for many meanings. For example, address and name (in practice more meanings).
Let's say the address and name have a default value.
void set_info(std::string address, std::string name) {
// set address and name
}
void set_info(std::string address) {
// set address and set default name
}
void set_info(std::string name) {
// set name and set default address
}
The target is not only function but also class constructor.
struct info {
info(std::string address, std::string name)
: address_{std::move(address)}, name_{std::move(name)}
{}
info(std::string address)
: address_{std::move(address)}
{}
info(std::string name)
: name_{std::move(name)}
{}
std::string address_;
std::string name_;
};
However, the latter two overloads are the same(Only difference is parameter name). So I can't do that.
So I come up with strong typedef of std::string helps the situation.
If each information has different types, it can avoid accidental type match. I expect that it could help "difficult to misuse" interface. The motivation is something like Boost.Unit https://www.boost.org/doc/libs/1_76_0/doc/html/boost_units.html
Is there any good way to write strong typedef of std::string?
My requirement are here:
void foo(type1) {}
void foo(type2) {}
I defined the following classes:
struct type1 : std::string {
using std::string::string;
};
struct type2 : std::string {
using std::string::string;
};
It works well for the requirement 1. However, the return type of operator+() is std::string, not strong typedef-ed type.
https://wandbox.org/permlink/6GAlcCoZi8XvumnZ
#include <string>
#include <iostream>
struct type1 : std::string {
using std::string::string;
};
struct type2 : std::string {
using std::string::string;
};
inline void foo(type1 const& v) {
std::cout << "type1:" << v << std::endl;
}
inline void foo(type2 const& v) {
std::cout << "type2:" << v << std::endl;
}
int main() {
type1 t1 = "ABC";
type2 t2 = "DEF";
foo(t1);
foo(t2);
// The return type of t1 + "123" is std::string, not type1
static_assert(std::is_same_v<decltype(t1 + "123"), std::string>);
// So it is not valid code to call foo(type1 const&)
// foo(t1 + "123");
}
I'm looking for a way to implement the operator+() that satisfies the following code:
int main() {
using namespace std::string_literals;
type1 m1 = "abc";
type2 m2 = "def";
// expected foo(type1 const&) is called
foo(m1);
foo(m1 + "a");
foo("a" + m1);
foo(m1 + "a"s);
foo("a"s + m1);
// expected foo(type2 const&) is called
foo(m2);
foo(m2 + "a");
foo("a" + m2);
foo(m2 + "a"s);
foo("a"s + m2);
std::string s = "a"s + "b"; // not become error
}
I tried to implement something like following operator+() but it causes ambiguous overloads.
template <typename T>
inline
std::enable_if_t<
std::is_convertible_v<T, std::string> &&
(!std::is_base_of_v<std::string, std::decay_t<T>> ||
std::is_same_v<std::decay_t<T>, std::string>),
type1
>
operator+(type1 lhs, T&& rhs) {
lhs += std::forward<T>(rhs);
return lhs;
}
template <typename T>
inline
std::enable_if_t<
std::is_convertible_v<T, std::string> &&
(!std::is_base_of_v<std::string, std::decay_t<T>> ||
std::is_same_v<std::decay_t<T>, std::string>),
type1
>
operator+(T&& lhs, type1 const& rhs) {
type1 ret{std::forward<T>(lhs)};
ret += rhs;
return ret;
}
I guess that if I implement all combinations of operator+() overloads for each strong typedef-ed types (type1, type2,...) but I expect that there is better way.
Upvotes: 1
Views: 868
Reputation: 130
I use private inheritance to achieve this goal. I need to add using statements on base members I want to see public. This can be seen as a burden, or a design advantage by which you can control what you want to expose. My 2 Cents.
Upvotes: 0
Reputation: 238361
You won't need to wrap every operation of std::string because you won't need to use them. All that the caller has to do is initialise the argument with a correct type with a value for the internal string:
struct address_type {
std::string value;
};
struct name_type {
std::string value;
};
struct info {
info(address_type address, name_type name)
: address_{std::move(address.value)}, name_{std::move(name.value)}
{}
info(address_type address)
: address_{std::move(address.value)}
{}
info(name_type name)
: name_{std::move(name.value)}
{}
private:
std::string address_;
std::string name_;
};
info with_address {address_type{"str"}};
info with_name {name_type {"str"}};
But if you want to proceed with your approach of providing string functionality with your custom type, then you must wrap all the operations. That's going to be a ton of boilerplate and there's no magic to avoid that.
Upvotes: 3