Simple c++ triple class to mimic pair for use with uniform initialization I'm trying to create a simple triplet class, but the more I look at the implementation of std::pair the more I feel like I'm missing important details.  What I have feels "too simple".  I am aware of std::tuple and other options, but the use case is to be able to use uniform initialization -- so we can make certain assumptions / ignore complications (?)

I am restricted to c++11, none of those beautiful c++14/17 magical unicorns added.

/// keeping class and test in self-contained file {test.cpp}
/// Compile: g++ -o test -std=c++11 test.cpp
#include &lt;iostream&gt;
#include &lt;vector&gt;

/// class definition
template &lt;class T1, class T2, class T3&gt;
struct triple {
    /// q1
    typedef T1 first_type;
    typedef T2 second_type;
    typedef T3 third_type;

    T1 first;
    T2 second;
    T3 third;

    /// q2
    triple() : first(), second(), third() {}

    triple(const T1 &amp;f, const T2 &amp;s, const T3 &amp;t)
        : first(f), second(s), third(t) {}

    /// q3
    template&lt;class U1, class U2, class U3&gt;
    triple(const triple&lt;U1, U2, U3&gt; &amp;t)
        : first(t.first), second(t.second), third(t.third) {}

    triple(const triple&lt;T1, T2, T3&gt; &amp;t) = default;
    triple(triple&lt;T1, T2, T3&gt; &amp;&amp;t) = default;
};

/// simple function to "use" these
void print(const std::vector&lt;triple&lt;int, const std::string, const std::string&gt;&gt; &amp;vals) {
   for(auto &amp;tup : vals) {
        std::cout &lt;&lt; "First:  " &lt;&lt; tup.first  &lt;&lt; std::endl
                  &lt;&lt; "Second: " &lt;&lt; tup.second &lt;&lt; std::endl
                  &lt;&lt; "Third:  " &lt;&lt; tup.third  &lt;&lt; std::endl;
        std::cout &lt;&lt; "------------------" &lt;&lt; std::endl;
   }
}

/// simple main, goal: uniform initialization
int main(int argc, char **argv) {
    print({
        {1, "hi", "billy"},
        {2, "you", "cool"},
        {3, "no", "not"}
    });
}


q1: the purpose of typedef

I know it's kind of old, but it's easier to explain from this implementation -- I'm looking at the implementation for clang.  Anyway, they all do this

typedef _T1 first_type;    ///&lt;  @c first_type is the first bound type
typedef _T2 second_type;   ///&lt;  @c second_type is the second bound type


and subsequently never use them.  What is the purpose, just to be able to do something like typename std::pair&lt;int, float&gt;::second_type f = 0.012f;?

q2: default constructors

My ultimate purpose is to just enable uniform initialization of something instead of a (large) number of std::make_tuple calls.  In this context, it seems reasonable to include some static_assert for


is_trivially_constructible
is_trivially_default_constructible
is_trivially_move_constructible
is_trivially_destructible


Does adding these make sense or is that over-limiting for no real reason?

q3: reflection construction?

Concerning the constructor with U1, U2, and U3, the purpose of this is to allow for automatic conversions?  Say automatically converting a triple&lt;unsigned int, float, bool&gt; -> triple&lt;size_t, double, int&gt;?

q4: any alarms going off?

Like I said, this is intended to be a simple and non-robust class.  I've been playing around with it, no memory leaks I can find, etc.  The operator overloads would be easy to put in, but other than that is there anything I should be more concerned with?  The stl implementation of pair is much more complex, and being that I ditched std::tuple so that I can do uniform initialization, this makes me think I'm missing something.
While the code you have appears to work as you expect, your stated reason for using it doesn't make sense to me, because one could use std::tuple with uniform initialization as well.

Use std::tuple

With only a relatively minor change to your print routine, the main you have could be used just as easily with std::tuple:

void print(const std::vector&lt;std::tuple&lt;int, const std::string, const std::string&gt;&gt; &amp;vals) {
   for(auto &amp;tup : vals) {
        std::cout &lt;&lt; "First:  " &lt;&lt; std::get&lt;0&gt;(tup) &lt;&lt; std::endl
                  &lt;&lt; "Second: " &lt;&lt; std::get&lt;1&gt;(tup) &lt;&lt; std::endl
                  &lt;&lt; "Third:  " &lt;&lt; std::get&lt;2&gt;(tup) &lt;&lt; std::endl;
        std::cout &lt;&lt; "------------------" &lt;&lt; std::endl;
   }
}


Answer 1

The reason those typedefs are there is that they're required by the standard.  That way, it's easy to be able to create other variables of the same type as, say, the second argument, without having to jump through a lot of hoops.  In other words, it's a convenience for the user of the template rather than for the author of the template.  

Answer 2

I wouldn't bother with static asserts.  If you did want to restrict the kinds of things that can be stored, it's probably better to use std::enable_if.  

Answer 3

I'm not sure, but I suspect that the other types U1 and U2 are required for convertable but non-identical classes that might be used to construct the triple (as you've surmised).

Answer 4

Only one alarm -- why aren't you using std::tuple?  :)

Try it live.