我必须定义一些包含
std::string
类型成员的结构。我需要让他们表现得像std::string
。换句话说,它们应该可以轻松地转换为 std::string
,但不能相互转换。这些结构的实例又将成为另一个类的成员:
class Foo
{
Name_t m_name;
Description_t m_description;
// ...
};
因此,采用
Name_t
类型参数的函数不应使用 Description_t
实例进行调用,即使它们都是 std::string
在幕后。
如何以适当的方式实现这一目标?我尝试为所有这些类型重载
operator std::string&
。不过这似乎有效。我不确定是否应该使用 explicit
关键字来声明它们。
一个例子:
#include <type_traits>
#include <utility>
#include <string>
#include <cassert>
struct Description_t
{
std::string value;
operator std::string&( ) noexcept
{
return value;
}
operator const std::string&( ) const noexcept
{
return value;
}
};
bool operator==( const Description_t& lhs, const Description_t& rhs ) noexcept
{
// This using declaration looks too verbose to my eyes!
using underlying_type = std::add_lvalue_reference_t<
std::add_const_t<
decltype( std::declval<decltype( lhs )>( ).value )> >;
return static_cast<underlying_type>( lhs ) == static_cast<underlying_type>( rhs );
// return lhs.value == rhs.value; // equivalent to the above statement
}
struct Name_t
{
std::string value;
operator std::string&( ) noexcept
{
return value;
}
operator const std::string&( ) const noexcept
{
return value;
}
};
bool operator==( const Name_t& lhs, const Name_t& rhs ) noexcept
{
// Again, this using declaration looks too verbose to my eyes!
using underlying_type = std::add_lvalue_reference_t<
std::add_const_t<
decltype( std::declval<decltype( lhs )>( ).value )> >;
return static_cast<underlying_type>( lhs ) == static_cast<underlying_type>( rhs );
// return lhs.value == rhs.value; // equivalent to the above statement
}
int main()
{
Description_t stock1 { "Hi!" };
Description_t stock2 { "Hi!" };
Name_t name1 { "Hello!" };
Name_t name2 { "Hello!" };
assert( name1 == name2 );
// assert( name1 == stock1 );
}
但是,在
underlying_type
的定义中,我必须通过添加 ref和
const来对
static_cast
进行 const std::string&
操作。否则,编译器将在 operator==
内生成两个低效的复制结构来比较两个 value
参数的 Name_t
。有没有更简洁的方法来做到这一点(除了lhs.value == rhs.value
)?
在上述情况下,
operator==
也应该是成员函数吗?
如果您想要一个行为类似于字符串的类型化字符串,带有 std::string 继承的 CRTP 将是一个选项:
template <typename>
struct TypeSafeString : std::string {
using std::string::string;
};
struct Description : TypeSafeString<Description> {
using TypeSafeString<Description>::TypeSafeString;
};
struct Name : TypeSafeString<Name> {
using TypeSafeString<Name>::TypeSafeString;
};
A
std::string
始终会转换为 Description
和 Name
,但 Description
不会转换为 Name
或 std::string
(反之亦然)。您还可以避免 CRTP 并通过 using
定义类型,但随后您必须为每种类型提供有保证的不同模板参数,这可能会出现一些问题。