(重新)命名为std :: pair成员

时间:2011-05-21 19:54:59

标签: c++ templates stl typedef traits

我想写town->first而不是写town->name。内联命名访问器(Renaming first and second of a map iteratorNamed std::pair members)是迄今为止我找到的最佳解决方案。我对命名访问器的问题是类型安全性的损失: pair<int,double>可以引用struct { int index; double value; }struct { int population; double avg_temp; }。任何人都可以提出一个简单的方法,也许类似于特征吗?

我经常想从一个函数中返回一对或一个元组,每次引入一个像struct city { string name; int zipcode; }这样的新类型及其ctor是非常累人的。我很高兴能够了解boost和C ++ 0x但我需要一个没有提升的纯C ++ 03解决方案。

更新

Re andrewdski的问题:是的,像({1}}这样的(假设的)语法可以创建一个来自pair<int=index, double=value>的独特类型,符合您的要求。我甚至不介意实现一个自定义的对/元组模板类ONCE,并且当我需要一个新类型时,只是将'name traits'模板参数传递给它approprietly。我不知道“名称特征”会是什么样子。也许这是不可能的。

10 个答案:

答案 0 :(得分:18)

我看不出你怎么能做得比

更好
struct city { string name; int zipcode; };

那里没有什么不必要的。你需要这两个成员的类型,你的整个问题都是以给两个成员的名字命名,你希望它是一个独特的类型。

你知道聚合初始化语法,对吗?你不需要构造函数或析构函数,编译器提供的就好了。

示例: http://ideone.com/IPCuw


类型安全要求您引入新类型,否则pair<string, int>在(名称,邮政编码)和(人口,临时)之间是不明确的。

在C ++ 03中,返回一个新元组需要:

city retval = { "name", zipcode };
return retval;

或编写便利构造函数:

city::city( std::string newName, int newZip ) : name(newName), zipcode(newZip) {}

获取

return city("name", zipcode);

但是,使用C ++ 0x,您将被允许编写

return { "name", zipcode };

并且不需要用户定义的构造函数。

答案 1 :(得分:5)

虽然不完美,但可以使用标记数据:

template <typename tag_type, typename pair_type>
typename tag_type::type& get(pair_type& p);

typedef std::pair<std::string /*name*/, int /*zipcode*/> city;
struct name { typedef std::string type; };
struct zipcode { typedef int type; };

template <>
std::string& get<name, city>(city& city)
{
   return city.first;
}

template <>
int& get<zipcode, city>(city& city)
{
   return city.second;
}

int main()
{
   city c("new york", 10001);
   std::string n = get<name>(c);
   int z = get<zipcode>(c);
}

但正如Ben Voigt所说:struct city { string name; int zipcode; };几乎总会更好。

编辑:模板可能有点过分,你可以在命名空间中使用自由函数。这仍然无法解决类型安全问题,因为任何std::pair<T1, T2>与任何其他std::pair<T1, T2>的类型相同:

namespace city
{
   typedef std::pair<std::string /*name*/, int /*zipcode*/> type;

   std::string& name(type& city)
   {
      return city.first;
   }

   int& zipcode(type& city)
   {
      return city.second;
   }
}

int main()
{
   city::type c("new york", 10001);
   std::string n = city::name(c);
   int z = city::zipcode(c);
}

答案 2 :(得分:3)

由于std::pair通常用于在std::map容器中存储条目,因此您可能需要查看tagged elements in Boost Bimap

梗概:

#include <boost/bimap/bimap.hpp>
#include <string>
#include <iostream>

struct name {}; // Tag for the default 'first' member
struct zipcode {}; // Tag for the default 'second' member

int main()
{
    using namespace boost::bimaps;
    typedef bimap <tagged<std::string, name>, tagged<int, zipcode> > Cities;
    typedef Cities::value_type registration;

    Cities cities;
    cities.insert(registration("Amsterdam", 20));
    cities.insert(registration("Rotterdam", 10));

    // ...
    std::string cityName;
    std::cin >> cityName;

    Cities::map_by<name>::const_iterator id_iter = cities.by<name>().find(cityName);
    if( id_iter != cities.by<name>().end() )
    {
        std::cout << "name: " << id_iter->get<name>() << std::endl
                  << "zip: " << id_iter->get<zipcode>()   << std::endl;
    }

    return 0;
}

请注意,bimaps可以透明地模拟std::map或其他关联容器类型而不会降低性能;他们只是更灵活。在这个特定的例子中,定义最有可能最好改为:

typedef bimap <tagged<std::string, name>, multiset_of<tagged<int, zipcode> > > Cities;
typedef Cities::value_type registration;

Cities cities;
cities.insert(registration("Amsterdam", 20));
cities.insert(registration("Rotterdam", 10));
cities.insert(registration("Rotterdam", 11));

我邀请您浏览Boost Bimap的文档以获取完整的图片

答案 3 :(得分:3)

我想详细说明

struct City : public std::pair<string, int> {
  string& name() { return first; }
  const string& name() const { return first; }
  int& zip() { return second; }
  int zip() const { return second; }
};

是你最接近你想要的东西,通过struct City { string name; int zipcode; }似乎完全没问题。

答案 4 :(得分:2)

您会很高兴知道the ranges proposal还包含一个名为tagged_pair的内容,以便您:

struct city { string name; int zipcode; };

也可以写成:

using city = tagged_pair<tag::name(std::string), tag::zipcode(int)>;

city c{"Chicago", 60654};
std::cout << c.name() << " is at zipcode " << c.zipcode() << '\n';

当然,这也可以直接用于返回类型:

tagged_pair<tag::min(int), tag::max(int)> get_range() {
     return {0, 100};
}

auto score_range = get_range();
std::cout << "From " << score_range.min() << " to " << score_range.max();

答案 5 :(得分:1)

您可以使用指向成员的运算符。还有一些选择。这是最直截了当的。

typedef std::map< zipcode_t, std::string > zipmap_t;
static zipcode_t const (zipmap_t::value_type::*const zipcode)
                                              = &zipmap_t::value_type::first;
static std::string (zipmap_t::value_type::*const zipname)
                                              = &zipmap_t::value_type::second;

// Usage
zipmap_t::value_type my_map_value;
std::string &name = my_map_value.*zipname;

您可以将一个伪类型的访问器放入专用的namespace中,以将它们与其他内容分开。然后它看起来像my_map_value.*zip::name。但是,除非您确实需要使用pair,否则定义新的struct可能会更容易。

答案 6 :(得分:1)

我想出了一个可以像这样使用的Utility_pair宏:

Utility_pair(ValidityDateRange,
    time_t, startDay,
    time_t, endDay
);

然后,只要您需要访问ValidityDateRange的字段,就可以这样做:

ValidityDateRange r = getTheRangeFromSomewhere();

auto start = r.startDay(); // get the start day
r.endDay() = aNewDay();    // set the end day
r.startDay(aNewDay1())     // set the start and end day in one go.
 .endDay(aNewDay2());

这是实施:

#include <utility>

#define Utility_pair_member_(pairName, ordinality, type, name)      \
    const type &name() const { return ordinality; }                 \
    type &name() { return ordinality; }                             \
    pairName &name(const type &m) { ordinality = m; return *this; } \
/***/

#define Utility_pair(pairName, firstMemberType, firstMemberName, secondMemberType, secondMemberName) \
    struct pairName: std::pair<firstMemberType, secondMemberType>  {                                 \
        Utility_pair_member_(pairName, first, firstMemberType, firstMemberName)                      \
        Utility_pair_member_(pairName, second, secondMemberType, secondMemberName)                   \
    }                                                                                                \
/***/

答案 7 :(得分:0)

我认为你应该在这里引入新类型。在完成配对课程方面,我完全处于stl方面,但这就是为什么java人们认为他们不想拥有一对类而且你应该总是为类似piar的类型引入新类型的确切原因。

关于stl解决方案的好处是你可以使用泛型类对,但只要你真的希望以不同于第一个/第二个的方式命名成员,你就可以引入新的类型/类。除此之外,如果有必要,可以自由地添加一个thrid成员。

答案 8 :(得分:0)

可能不值得使用额外的内存,但是如果您想保留std::pairstd::tuple /的优势/保持与不变API的兼容性,则可以从它们继承,然后定义对其成员的引用

除了内存成本外,这还带来了一个巨大的警告,即STL类型通常没有虚拟析构函数,因此,如果您尝试使用指向基本类型的指针进行分配,则可能导致内存泄漏:

#include <tuple>

struct PairShadow : public std::pair<int, double> {
    using std::pair<int, double>::pair;
    PairShadow & operator=(PairShadow const &) { /*call parent op here*/ }
    int & x = this->first;
    double & y = this->second;
};

struct TupleShadow: public std::tuple<int, double> {
    using std::tuple<int, double>::tuple;
    PairShadow & operator=(PairShadow const &) { /*call parent op here*/ }
    int & x = std::get<0>(*this);
    double & y = std::get<1>(*this);
};

auto main() -> int {
    auto twinShadow = PairShadow(1,3.0);
    auto tupleShadow = TupleShadow(1,3.0);
    return tupleShadow.y;
}

或者,正如@holyblackcat所提到的那样,在很多情况下,只要您不尝试遵循不可更改的API(特别要求使用API​​),也可以在很多情况下为结构定义std::get<int>(PairShadow)的重载。 std::pairstd::tuple

答案 9 :(得分:-1)

也许你可以从pair继承你自己的pair类,并在你的构造函数中设置两个名为name和zipcode的引用(只需确保实现你将使用的所有构造函数)