首页 > 解决方案 > std::string 的强 typedef

问题描述

动机(问题背景)

我使用 std::string 有很多含义。例如,地址和名称(在实践中更多的含义)。

假设地址和名称具有默认值。

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
}

目标不仅是函数,而且是类构造函数。

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_;
};

但是,后两个重载是相同的(唯一的区别是参数名称)。所以我不能那样做。

所以我想出了 std::string 的强 typedef 有助于解决这种情况。

额外的动力

如果每个信息都有不同的类型,可以避免意外的类型匹配。我希望它可以帮助“难以滥用”的界面。动机类似于 Boost.Unit https://www.boost.org/doc/libs/1_76_0/doc/html/boost_units.html

问题

有什么好方法可以编写 std::string 的强 typedef 吗?

我的要求在这里:

  1. 可以编写多个强类型定义的重载。
    void foo(type1) {}
    void foo(type2) {}
    
  2. 可以像 std::string 一样操作,包括辅助操作符。至少需要 operator+()。运算符的工作方式应该类似于 https://en.cppreference.com/w/cpp/string/basic_string/operator%2B operator+() 的返回值应该是强类型定义的类型,而不是 std::string。

我定义了以下类:

struct type1 : std::string {
    using std::string::string;
};

struct type2 : std::string {
    using std::string::string;
};

它适用于要求 1。但是,operator+() 的返回类型是 std::string,而不是强 typedef-ed 类型。

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");
}

我正在寻找一种方法来实现满足以下代码的 operator+():

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
}

我试图实现类似 operator+() 的东西,但它会导致模棱两可的重载。

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;
}

我想如果我为每个强类型定义类型(type1、type2、...)实现 operator+() 重载的所有组合,但我希望有更好的方法。

标签: c++stringstrong-typedef

解决方案


您不需要包装 std::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"}};

但是,如果您想继续使用自定义类型提供字符串功能的方法,那么您必须包装所有操作。这将是大量的样板文件,没有什么魔法可以避免这种情况。


推荐阅读