36

Instead of writing town->first I would like to write town->name. Inline named accessors (Renaming first and second of a map iterator and Named std::pair members) are the best solutions I have found so far. My problem with named accessors is the loss of type safety: pair<int,double> may refer to struct { int index; double value; } or to struct { int population; double avg_temp; }. Can anyone propose a simple approach, perhaps something similar to traits?

I often want to return a pair or a tuple from a function and it is quite tiring to introduce a new type like struct city { string name; int zipcode; } and its ctor every time. I am thrilled to learn about boost and C++0x but I need a pure C++03 solution without boost.

Update

Re andrewdski's question: yes, a (hypothetical) syntax like pair<int=index, double=value> which would create a distinct type from pair<int=population, double=avg_temp> would meet your requirement. I do not even mind having to implement a custom pair/tuple template class ONCE and just passing a 'name traits' template argument to it approprietly when I need a new type. I have no idea how that 'name traits' would look like. Maybe it's impossible.

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
  • 5
    How is this not a real question? Why the downvote? – GManNickG May 21 '11 at 20:06
  • I would like to know that too. – Ali May 21 '11 at 20:09
  • @GMan - The downvote is for the thought that every object with two members should inherit from std::pair and rename the members. Do we need std::triple and std::quad for larger objects?! – Bo Persson May 21 '11 at 20:18
  • 2
    @Bo Persson: you should read the question first before downvoting: **where did I mention inheritence?** – Ali May 21 '11 at 20:20
  • 4
    @Bo Persson There is std::tuple / boost::tuple for larger objects. – Karl von Moor May 21 '11 at 20:23
  • 2
    Maybe the problem is people aren't quite understanding what you are asking for. You say you want to distinguish between `struct { int index; double value; }` and `struct { int population; double avg_temp; }`, but you want to do this simply by renaming pair members? How in the world would that work? It sure seems like you want a struct. (And for the record, I did not downvote.) – andrewdski May 21 '11 at 20:27
  • @andrewdski: Yes, you see my problem correctly. I would like to reuse std::pair, and rename its members SOMEHOW. If I knew the answer how, I would not ask this question. :) – Ali May 21 '11 at 20:31
  • @Karl - But these are for types with unnamed members. – Bo Persson May 21 '11 at 20:33
  • @Ali This could be a job for macros to create the structures you need, if you just want to save yourself a bit of typing each time – Chris Frederick May 21 '11 at 20:34
  • @Ali - How else would your types get at the members of a pair? – Bo Persson May 21 '11 at 20:34
  • 1
    @Bo Persson I agree – inheriting from std::pair or whatever is stupid. I just wanted to say why we do not need a std::triple and std::quad. – Karl von Moor May 21 '11 at 20:37
  • 2
    So a (hypothetical) syntax like `pair` which would create a distinct type from `pair` would meet your requirement? – andrewdski May 21 '11 at 20:41
  • 2
    @Bo: Downvoting without comment is justified because someone doesn't understand something or has a misunderstanding? You must have downvoted every question, then. (Note he never said anything about inheritance anyway, so I'm not quite sure what your point is.) – GManNickG May 21 '11 at 20:42
  • @GMan - I downvoted during the 30 minutes before he edited the question. It was just not comprehensible. How do you modify a class without inheriting from it?! – Bo Persson May 21 '11 at 20:49
  • @Bo Persson: I have never mentioned inheritence, you can easily check the history. By traits, you can attach info to classes, without changing them. I do not know how that would work in this case, hence the question. – Ali May 21 '11 at 20:53
  • @andrewdski: Yes. I do not even mind having to implement a custom pair/tuple template class ONCE and just passing a 'name traits' to it approprietly at each case I need a new type. However, reusing std::pair is preferred. – Ali May 21 '11 at 20:56
  • @Ali - You can't change member names with traits, can you? And you can't modify a class' behavior without inheriting and override virtual functions. I just found the initial question really silly because of this, but obviously people disagree. – Bo Persson May 21 '11 at 21:01
  • @Bo Persson: in the original question, where did I write about changing behavior? – Ali May 21 '11 at 21:08
  • Hmmm it appears I missed the part where you said you didn't want to employ boost :) in that case @dalle's answer should be ok (other than the 'just use a struct' choir, that is right but boring) – sehe May 22 '11 at 08:26

10 Answers10

26

I don't see how you can possibly do better than

struct city { string name; int zipcode; };

There's nothing non-essential there. You need the types of the two members, your whole question is predicated around giving names to the two members, and you want it to be a unique type.

You do know about aggregate initialization syntax, right? You don't need a constructor or destructor, the compiler-provided ones are just fine.

Example: http://ideone.com/IPCuw


Type safety requires that you introduce new types, otherwise pair<string, int> is ambiguous between (name, zipcode) and (population, temp).

In C++03, returning a new tuple requires either:

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

or writing a convenience constructor:

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

to get

return city("name", zipcode);

With C++0x, however, you will be allowed to write

return { "name", zipcode };

and no user-defined constructor is necessary.

Taryn
  • 242,637
  • 56
  • 362
  • 405
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 3
    You beat me to it. I should add however that with `string` the aggregate initialization syntax will not work. – Alexandre C. May 21 '11 at 22:39
  • 2
    might be good to expand on aggregate initialization syntax for the general google audience – Doug T. May 21 '11 at 22:40
  • @Ben: I didn't know it worked with `std::string` inside. Gcc accepts it with `-ansi`. Please don't take my first comment into account then. – Alexandre C. May 21 '11 at 23:50
  • @Alexandre: It would be quite problematic if this wasn't allowed: array initialization uses the same *aggregate initializer*. – Ben Voigt May 21 '11 at 23:57
  • @Ben Voigt: Upvoted and thanks. Unfortunately extended initializer list is not available in C++03, you cannot write `return { "new york", 10001 };` at the end of a function. So you still have to write a ctor and write `return city("new york", 10001);`. I need a pure C++03 solution. – Ali May 22 '11 at 09:42
  • @Ali: What's wrong with `city retval = { "new york", 10001 }; return retval;` ? After "Named Return Value Optimization", which pretty much all modern compilers do, it'll be the same. – Ben Voigt May 22 '11 at 09:55
  • @Ben Voigt: Partly my lack of knowledge. :) On the other hand, I would rather write a ctor once than write an extra line every time a city is returned. C++0x will solve this problem. Thanks! – Ali May 22 '11 at 10:04
  • @Ben Voigt: I would like to accept your answer but please add the followings to it. (1) You have to introduce new types otherwise you cannot have both `typedef pair city;` and `typedef pair building;` (2) In C++0x you will be able to write `return { "new york", 10001 };` (3) In the meantime you either write the ctor once or add an extra line `city retval = { "new york", 10001 }; return retval;` every time a city is returned. Thanks! – Ali May 22 '11 at 10:59
  • @BenVoigt The link for ideone is dead and not available on the internet archive - do you know of a replacement? – Taryn Jul 14 '14 at 10:59
  • @bluefeet: No, it's unfortunate that ideone's ToS declare that it is a place to publish source code snippets "Forever", but in reality they destroy user-owned material and do not respond to inquiries. I've basically stopped using it for that reason. – Ben Voigt Jul 14 '14 at 14:52
  • @BenVoigt Sorry for the late reply but have you had this issue with coliru at all? That is what I primarily use for live examples. – NathanOliver Jul 15 '15 at 14:39
6

I guess elaborating on

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

is the closest you get to what youre looking for, althrough struct City { string name; int zipcode; } seems perfectly fine.

Viktor Sehr
  • 12,825
  • 5
  • 58
  • 90
  • 2
    quoting the question: _Inline named accessors ([1](http://stackoverflow.com/q/1500064/341970), [2](http://compgroups.net/comp.lang.c++.moderated/Named-std-pair-members)) are the best solutions I have found so far_ – sehe May 21 '11 at 23:05
5

Although not perfect, it is possible to use tagged data:

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

But as Ben Voigt says: struct city { string name; int zipcode; }; would pretty much always be better.

EDIT: Templates probably are an overkill, you could use free functions in a namespace instead. This still does not solve type safety issues, as any std::pair<T1, T2> are the same type as any other 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);
}
dalle
  • 18,057
  • 5
  • 57
  • 81
  • 1
    Gonna have to upvote this... it is precisely what my suggestion (boost::bimaps) does – sehe May 21 '11 at 23:06
  • Thanks for your efforts to answer my question. However I still do not get it. Why do you have those typedefs in the structs? They seem to be unused. And the type safety issue is not solved, you cannot have another `typedef std::pair building;`. That was my problem with named accessors. – Ali May 22 '11 at 09:51
  • @Ali: All `std::pair` are the same type as any other `std::pair`. If you want type safety you need to use different types. – dalle May 22 '11 at 10:04
  • Yes, I know. I do not even mind having to implement a custom pair/tuple template class ONCE and just passing a 'name traits' template argument to it approprietly when I need a new type. I have no idea how that 'name traits' would look like. Maybe it's impossible. Do you see what I would like to have? – Ali May 22 '11 at 10:12
  • upvoted your answer and thanks. Maybe a plain `struct city { string name; int zipcode; };` is really the simplest solution, and with C++0x I will be able to use aggregate initialization. – Ali May 22 '11 at 10:44
4

Since std::pair is commonly used for storing entries in std::map containers, you might want to look at tagged elements in Boost Bimap.

Synopsis:

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

Note that bimaps can transparently emulate std::map or other associative container types without performance cost; They just are more flexible. In this particular example, the definition would most likely best be changed into something like:

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

I invite you to wander around the documentation for Boost Bimap to get the full picture

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks for your answer. I am happy to learn more about boost. Please see my comment sehe's answer, I have some problems understanding it. – Ali May 22 '11 at 09:54
1

You can use pointer-to-member operators. There are a few alternatives. Here is the most straightforward.

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;

You can put the accessors for one pseudo-type into a dedicated namespace to separate them from other things. Then it would look like my_map_value.*zip::name. But, unless you really need to use a pair, it's probably easier to just define a new struct.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Yes, I believe the code is the easiest to understand if one defines a new struct. If I have to use pairs, I will use inline accessors. Anyway, thanks for the answer, upvoted! – Ali Jan 02 '13 at 17:45
1

I came up with a Utility_pair macro that can be used like this:

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

Then, whenever you need to access the fields of ValidityDateRange, you can do it like this:

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());

This is the implementation:

#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)                   \
    }                                                                                                \
/***/
Fabio A.
  • 2,517
  • 26
  • 35
1

Probably not worth the extra memory, but if you want to retain the advantage of std::pair or std::tuple/ maintain compatibility with an unchangeable API, you can inherit from them and then define references to their members.

Apart from the memory cost, this comes with the giant caveat that STL types generally don't have virtual destructors and can thus cause memory leaks if you attempt to de-allocate using a pointer to the base type:

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

Alternatively, and perhaps preferrably as mentioned by @holyblackcat you could also just define an overload to std::get<int>(PairShadow) for your struct in many cases as long as you aren't trying to conform to an unchangable API which specifically requires a std::pair or std::tuple

Catskul
  • 17,916
  • 15
  • 84
  • 113
  • This comes with memory (and probably performance) overhead, and makes the tuple non-assignable. IMHO a better option is to use a regular `struct`, like the accepted answer suggests, and possibly specialize `std::get` (and related functions) for it. – HolyBlackCat Sep 13 '19 at 16:22
  • I very much agree that it's probably better to use the regular struct, and this has unnecessary memory cost, the assignment issue however can be resolved by defining the assignment operator though clearly with an annoying lose of concision. I'll add some more caveats. – Catskul Sep 13 '19 at 18:23
1

Old question, but adding an update for C++17 where you can use Structured binding declaration for naming the members of std::pair and std::tuple. For example:

std::list<std::pair<std::string, long>> getCitiesAndPopulations();
. . . 
for(const auto& [city, population] : getCitiesAndPopulations())
    std::cout << "City: " << city << ", population: " << population << std::endl;

Note: This doesn't address the type safety aspect.

JukkaA
  • 191
  • 1
  • 5
0

I think you should really introduce new types here. I am totally on stl's side in terms of having a pair class, but this is the exact reason why java people argue they don't want to have a pair class and you should always introduce new types for your piar-like types.

The good thing about the stl solution is that you can use the gerneric class pair, but you can introduce new types / classes whenever you really want members to be named in a different way than first/second. On top of that introducing new classes gives you the freedom of easily adding a thrid member if it should ever become necessary.

b.buchhold
  • 3,837
  • 2
  • 24
  • 33
-1

perhaps you can inherit your own pair class from pair and set two references called name and zipcode in your constructor ( just be sure to implement all constructors you will use )

V_shr
  • 39
  • 5
  • 4
    `std::pair` does not have a virtual destructor, so this could lead to accidental memory leaks. – Travis Gockel May 21 '11 at 20:14
  • 1
    Thanks. But if you introduce a new type, why not introduce struct city { string name; int zipcode; } in the first place? – Ali May 21 '11 at 20:16
  • @Travis: yes, you should declare your own destructors (which will call parent's destructors) as well as constructors @Ali: by inheriting from pair you can use all its functionality ( for example when a predefined function needs pair as it's argument, compiler will autocast your type to pair if you use your ownPair inherited from pair ) – V_shr May 21 '11 at 20:19
  • 2
    That's not the problem. If *anything* gets added to the class that requires destruction, those destructors will not be called by anything that only takes an `std::pair` http://www.codersource.net/c/c-miscellaneous/c-virtual-destructors.aspx – Travis Gockel May 21 '11 at 21:03