0

I'm using a pair type:

typedef std::pair<int, std::string> Nomnom;

Example places it's used:

void DoIt(const Nomnom &toNom) { 
    ...
}
void DoItAgain(const Nomnom &toNomAgain) { 
    ...
}

In my case it makes sense to offer a default value for the pair if it isn't specified. That is, I want to be able to do this:

DoIt("Thisisit");
DoItAgain("Thisisit");

and have it be equivalent to this:

DoIt(NomNom(0, "Thisisit"));
DoItAgain(NomNom(0, "Thisisit"));

I want to do this without defining two variants every time a Nomnom is used.

Is there any way to accomplish this short of defining a class for Nomnom instead of the typedef, providing a one-arg constructor, and then providing the zero-arg and two-arg constructors that call the std::pair constructors?

Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • Yes it is called overloading. You can have both `ClassNoms(Nomnom oneIn)` and `ClassNoms(int key)`. – IdeaHat Apr 02 '14 at 17:57
  • Depends on which reasonable default for the `int` you can apply, doesn't it? In this case you could easily get off using a default constructor `ClassNoms() : one(std::make_pair(-1,"")`. Or did I simply miss what you're asking for? – πάντα ῥεῖ Apr 02 '14 at 17:57
  • Sorry it wasn't clear. I want a default constructor for `Nomnom`, not for `ClassNoms`. Note that `DoIt` takes a `Nomnom`, not a `ClassNoms`. I'll fix up the examples. – Claudiu Apr 02 '14 at 17:59
  • 2
    You cannot add your own constructors do an existing `std` class easily (inheritance is troublesome). What you can do is provide overloads for `DoIt` and `DoItAgain` though. – pmr Apr 02 '14 at 18:03

3 Answers3

2

ORIGINAL QUESTION

Is there any way to accomplish this short of defining a class for Nomnom instead, providing a one-arg constructor, and then providing the zero-arg and two-arg constructors that call the std::pair constructors?

You could have more constructor overloads as

ClassNoms() : one(std::make_pair(-1,"") {}
ClassNoms(int id) : one(std::make_pair(id,"") {}
ClassNoms(const std::string& name) : one(std::make_pair(-1,name) {}
ClassNoms(int id, const std::string& name) : one(std::make_pair(id,name) {}

UPDATE:

Is there any way to accomplish this short of defining a class for Nomnom instead of the typedef, providing a one-arg constructor, and then providing the zero-arg and two-arg constructors that call the std::pair constructors?

typedef std::pair<int, std::string> Nomnom;

You can't provide a default constructor for your typedef'd NomNom, because std::pair<> doesn't provide one. To achieve this, you'll need to either inherit a class NomNom from std::pair<>, or IMHO better provide a wrapper for it, and use this one:

template<typename First,typename Second>
struct DefaultValuedPair {
     std::pair<First,Second> value;
     First& first;
     Second& second;
     DefaultValuedPair
        ( const First& first = First()
        , const Second& second = Second()
        ) 
     : value(std::make_pair(first,second))
     , first(value.first)
     , second(value.second) {}
     DefaultValuedPair(const std::pair<First,Second>& rhs) 
     : value(rhs)
     , first(value.first)
     , second(value.second) {}
     // Add more implicit type conversions for std::pair<First,Second>
     // as necessary ...
};

And have

typedef DefaultValuedPair<int, std::string> Nomnom;

You could have a function that acts like a constructor, but it can't be named and called as pure equivalent for a NomNom (declared) type, and this wouldn't give any advantage over using std::make_pair().

The proposed wrapper solution might have a huge impact on considerations of refactoring already existing code that uses NomNom, and should work seamlessly with such. As mentioned, just replacing/disguising std::make_pair per se, makes no sense IMHO.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • Ya check my edit, this isn't what I was asking. Pretend I never mentioned `ClassNoms`. – Claudiu Apr 02 '14 at 18:00
  • @Claudiu It is the same idea. Just use `make_pair` as shown here. – juanchopanza Apr 02 '14 at 18:02
  • I asked if there was a way to do this **besides** changing the typedef to a class, and then having to provide all the constructors. Your answer just shows how to provide all the constructors.. – Claudiu Apr 02 '14 at 18:04
  • Ah okay looks like that's a no then! – Claudiu Apr 02 '14 at 18:06
  • @Claudiu OK, I made a more solid proposal for a wrapper class. – πάντα ῥεῖ Apr 02 '14 at 18:26
  • @Claudiu In case you are still interested, I've tweaked my answer towards something you may have wanted originally instead of a function. – πάντα ῥεῖ Apr 02 '14 at 20:33
  • @πάνταῥεῖ: Thanks for all the effort you put in! I upvoted your answer a bit ago. It looks like the only options were the two I was wanting to avoid: to either make two variants of each function or to make a class like you did here. Probably the wrapper would be the way to go if I used the pattern a lot. I'm going with the 2-variant approach for now – Claudiu Apr 02 '14 at 21:15
2

You cannot easily add your own constructor to std::pair (inheriting from it isn't really an option).

But you can provide a small function to construct it and overload it accordingly:

Nomnom nomnom(const std::string& s) { return Nomnom(-1, s); }
Nomnom nomnom(int i) { return Nomnom(i, ""); }

You could also overload DoIt and DoItAgain and forward on a common implementation.

void DoIt(const Nomnom& n) { /* ... */ }
void DoIt(int i) { DoIt(Nomnom(i, "")); }
pmr
  • 58,701
  • 10
  • 113
  • 156
  • Oh heh that makes sense. I thought of doing it, decided not to for some reason because I wanted a constructor, but for the code that uses it it looks much the same – Claudiu Apr 02 '14 at 18:07
  • 2
    Claudia, `make_nomnom` would also be a good name, but I went for the shorter version. But if your `Nomnom` really has some invariants instead of just being a pair you really want to consider using a class. – pmr Apr 02 '14 at 18:09
1

If you're using C++11, you can just inherit the constructors from pair with using. I think this is the cleanest solution to your problem.

Is there a reason (besides not wanting to re-implement std::pair's constructors) you don't want to inherit from std::pair?

#include <utility>
#include <string>
#include <iostream>

/**
 * Nomnom is just a std::pair with a customized constructor.
 */
class Nomnom : public std::pair <int, std::string> {
  typedef std::pair<int, std::string> parent_type; // For convenience

  public:
  using parent_type::parent_type; // Inherit Pair's constructors
  Nomnom (std::string S) : parent_type(0, S) {} // And add our own
  Nomnom (char const * S) : parent_type(0, S) {} // Handle C strings...
};

/**
 * Show that we can just use Nomnom as a std::pair...
 */
template <class FIRST, class SECOND> 
std::ostream & operator<< (std::ostream & out, std::pair <FIRST, SECOND> const & print_me) {
  return out << print_me.first << ", " << print_me.second << '\n';
}

/**
 * Use this to test initizlization while passing to a method
 */
void foo (Nomnom const & print_me) {
  std::cout << print_me;
}

int main (void) {
  std::cout << Nomnom("Hello!");
  std::cout << Nomnom(5, "World!");
  foo ("Mouse!");

  return 0;
}
QuestionC
  • 10,006
  • 4
  • 26
  • 44
  • Oh neat, didn't know about `using`. At first there was no reason, but when I tried it I realize there is - there are a set of functions that operate on all sorts of types, including both `pair` and `T`, so if I have a `T` that inherits from pair, I get an ambiguous resolution error – Claudiu Apr 02 '14 at 18:57
  • Let me add the obvious: inheriting from a type that does not have a virtual destructor is a bad idea. This goes for almost all standard library types. In addition, this breaks all kinds of template specializations the library might provide for the `pair`/`tuple` interface. – pmr Apr 03 '14 at 12:20