5
\$\begingroup\$

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 <iostream>
#include <vector>

/// class definition
template <class T1, class T2, class T3>
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 &f, const T2 &s, const T3 &t)
        : first(f), second(s), third(t) {}

    /// q3
    template<class U1, class U2, class U3>
    triple(const triple<U1, U2, U3> &t)
        : first(t.first), second(t.second), third(t.third) {}

    triple(const triple<T1, T2, T3> &t) = default;
    triple(triple<T1, T2, T3> &&t) = default;
};

/// simple function to "use" these
void print(const std::vector<triple<int, const std::string, const std::string>> &vals) {
   for(auto &tup : vals) {
        std::cout << "First:  " << tup.first  << std::endl
                  << "Second: " << tup.second << std::endl
                  << "Third:  " << tup.third  << std::endl;
        std::cout << "------------------" << 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;    ///<  @c first_type is the first bound type
typedef _T2 second_type;   ///<  @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<int, float>::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

  1. is_trivially_constructible
  2. is_trivially_default_constructible
  3. is_trivially_move_constructible
  4. 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<unsigned int, float, bool> -> triple<size_t, double, int>?

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.

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

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<std::tuple<int, const std::string, const std::string>> &vals) {
   for(auto &tup : vals) {
        std::cout << "First:  " << std::get<0>(tup) << std::endl
                  << "Second: " << std::get<1>(tup) << std::endl
                  << "Third:  " << std::get<2>(tup) << std::endl;
        std::cout << "------------------" << 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.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the response! Which compiler are you using? I can only get AppleClang to accept this. GCC 4.8, 5.3, and 6.2, as well as clang 3.8 and 4.0 have varying degrees of dissatisfaction with std::tuple. In all honesty I only made this class because I couldn't get std::tuple to behave. Some perusing online seemed to indicate general difficulty with std::tuple and uniform initialization, but I think that's only supposed to be for assignment statements? \$\endgroup\$
    – svenevs
    Commented Jun 28, 2017 at 17:02
  • \$\begingroup\$ I used both g++ 6.3.1 and clang++ 3.9 with no errors or warnings. I've also added live link to the answer. \$\endgroup\$
    – Edward
    Commented Jun 28, 2017 at 17:51
  • \$\begingroup\$ Hmmmm. I've been playing around with this for probably too long, I have no idea what I've done to not be able to do this but I agree that std::tuple should be working exactly as you have shown. So it shall be that your answer is accepted ;) Unrelated thanks for clarifying answer 1, that and this whole experience have made certain practices with templates make a lot more sense! \$\endgroup\$
    – svenevs
    Commented Jun 30, 2017 at 3:37
  • \$\begingroup\$ I wonder if the problems you've observed on your machine are related to this gcc bug? \$\endgroup\$
    – Edward
    Commented Jun 30, 2017 at 10:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.