C++ Antipatterns: The Java constructor (and final vs const)

It seems Java (or C#) constructors are becoming increasingly pervasive in novice C++ code. A demo of what I mean:

class Point {
  public:
    Point(int a, int b) {
      this->x_ = a;
      this->y_ = b;
    }
    //...
  private:
    int x_;
    int y_;
};

This looks normal for programmers coming from a java background, but for those with C++ experience, it’s awkward that the variables x_ and y_ are initialized inside the constructor body. C++ constructors using initialization lists are more correct. They go between the ) and { of the constructor, begin with a colon, and have each part separated by a comma. They style I lean towards is as follows:

Point(int a, int b)
    : x_{a},
    y_{b}
{ }

The difference is that rather than having x_ and y_ be default constructed, then assigned, they are copy-constructed with the values of a and b. These x_ and y_ values must have been constructed because upon entering the body of the constructor, the this object must exist.

Performance

Okay, so dealing with a couple of ints, no big deal. Let’s look at a class with a string

class Person {
  public:
    Person(const std::string& n) {
      this->name_ = n;
    }
  private:
    std::string name_;
};

The semantics of this are different from what novices often think. What happens is that name_ is default constructed, and is then assigned to, and overwritten, upon entering the constructor. This means that before being assigned n, name_ already has a value! It’s the empty string! Constructing and assigning is slower than just copy constructing, so there’s a slight performance hit.

The correct constructor is, as expected

Person(const std::string& n)
    : name_{n}
{ }

Objects without default constructors

Novices don’t care too much up to this point because strings and ints both can be default constructed, and strings behave very much like POD objects. However there are many objects in C++ which don’t have default constructors at all! An example? the Person class we just made!

How about a class which has a Person in it?

class Couple {
  public:
    // fails before entering { }
    Couple(const Person& p1, const Person& p2) {
      this->person1_ = p1;
      this->person2_ = p2;
    }
  private:
    Person person1_;
    Person person2_;
};

This can’t work, because the Person class doesn’t have a default constructor. the compiler error will resemble error: no matching function for call to Person::Person(). The assignments are valid in this case, but the construction just doesn’t work. We have to use C++ style:

Couple(const Person& p1, const Person& p2)
    : person1_{p1}, // works, uses implicit copy ctor
    person2_{p2}
{ }

Beautiful!

const, and why it’s different from final

Another case to consider, in java one may have a final instance variable, signalling that once the reference has been assigned (by the constructor) it cannot be reassigned. Some incorrectly draw a parallel between const and final, they aren’t the same.

Let’s consider a Person class where we also have a string for their social security number. This number can’t change, so we want it to be const. The java-style constructor fails here.

class Person {
  public:
   Person(const std::string& n, const std::string& s) {
     this->name_ = n;
     this->ssn_ = s; //fails, can't assign to const var
   }
  private:
   std::string name_;
   const std::string ssn_;
};

Remember, the semantics differ, this code default constructs ssn_ and then assigns to it. However, we can’t assign to const variables, we can only construct them (or if you prefer, initialize them). Instead we must use the correct C++ constructor style.

Person(const std::string& n, const std::string& s)
    : name_{n},
    ssn_{s} // works, construction not assignment!
{ }

Since C++ novices often don’t care about constness, it’s hard to convince them to take this seriously last one seriously.

Other cases

There are other types of objects that cause problems. One is the class of objects without an assignment operator (such as the Person with a const ssn), objects where default construction followed by assignment does get expensive (std::array for example), objects that can be moved but not copied need to be handled differently, etc.

This is not an academic exercise, the problems with java-style constructors are very real.

Another Exploding Tuple

After watching Andrei Alexandrescu’s talk on Going Native 2013, I wanted to take a crack at it myself. The presentation covers how to expand a tuple into individual arguments in a function call. Being a Python programmer I’m a little spoiled by func(*args) so the ability to do this in C++11 is something I’m eager to use. What I came up with wound up being quite similar, but more flexible. I wanted to make it more generic, to work with std::pair and std::array. The version presented in that video is incredibly powerful, but it can go a bit further.

The limitations start at the top level, the explode free function.

template <class F, class... Ts>
auto explode(F&& f, const tuple<Ts...>& t)
    -> typename result_of<F(Ts...)>::type
{
    return Expander<sizeof...(Ts),
      typename result_of<F(Ts...)>::type,
      F,
      const tuple<Ts...>&>::expand(f, t);
}

The tuple& argument allows a means to use result_of to figure out the return type, and sizeof... to determine the size of the tuple itself. This can be accomplished via other means. decltype can be used to figure out the return type. It needs more typing, but removes the need for result_of. As for sizeof..., there is a std::tuple_size available which can reach the same end. Using this makes explode non-variadic. Taking a universal reference, rather than capturing the parameter pack, means different versions for lvalue and rvalue refs aren’t needed.

My initial function (called expand instead) is:

template <typename Functor, typename Tup>
auto expand(Functor&& f, Tup&& tup)
  -> decltype(Expander<std::tuple_size<typename std::remove_reference<Tup>::type>::value, Functor, Tup>::call(
        std::forward<Functor>(f),
        std::forward<Tup>(tup)))
{
    return Expander<
        std::tuple_size<typename std::remove_reference<Tup>::type>::value, 
        Functor, 
        Tup>::call(
          std::forward<Functor>(f),
          std::forward<Tup>(tup));
}

Some things to note:

  1. std::tuple_size works on std::pair (yielding 2) and on std::array (yielding the size of the array).
  2. std::get also supports std::pair and std::array, meaning that now tuple, pair, and array can all work in this context.
  3. std::remove_reference is needed for calling std::tuple_size because tup is a universal reference, and Tup may deduce to an lvalue reference type

The decltype goes through each level of the expansion, until much like the original, it hits a base case and does the call.

#include <cstddef>
#include <tuple>
#include <utility>
#include <type_traits>
#include <array>

template <std::size_t Index, typename Functor, typename Tup>
struct Expander {
  template <typename... Ts>
  static auto call(Functor&& f, Tup&& tup, Ts&&... args)
    -> decltype(Expander<Index-1, Functor, Tup>::call(
        std::forward<Functor>(f),
        std::forward<Tup>(tup),
        std::get<Index-1>(tup),
        std::forward<Ts>(args)...))
  {
    return Expander<Index-1, Functor, Tup>::call(
        std::forward<Functor>(f),
        std::forward<Tup>(tup),
        std::get<Index-1>(tup),
        std::forward<Ts>(args)...);
  }
};

template <typename Functor, typename Tup>
struct Expander<0, Functor, Tup> {
  template <typename... Ts>
  static auto call(Functor&& f, Tup&&, Ts&&... args)
    -> decltype(f(std::forward<Ts>(args)...))
  {
    static_assert(
      std::tuple_size<
          typename std::remove_reference<Tup>::type>::value
        == sizeof...(Ts),
      "tuple has not been fully expanded");
    // actually call the function
    return f(std::forward<Ts>(args)...);
  }
};

template <typename Functor, typename Tup>
auto expand(Functor&& f, Tup&& tup)
  -> decltype(Expander<std::tuple_size<
      typename std::remove_reference<Tup>::type>::value, 
      Functor,
      Tup>::call(
        std::forward<Functor>(f),
        std::forward<Tup>(tup)))
{
  return Expander<std::tuple_size<
      typename std::remove_reference<Tup>::type>::value, 
      Functor,
      Tup>::call(
        std::forward<Functor>(f),
        std::forward<Tup>(tup));
}

A few examples showing the flexibility.

int f(int, double, char);
int g(const char *, int);
int h(int, int, int);

int main() {
    expand(f, std::make_tuple(2, 2.0, '2'));

    // works with pairs
    auto p = std::make_pair("hey", 1);
    expand(g, p); 

    // works with std::arrays
    std::array<int, 3> arr = {{1,2,3}};
    expand(h, arr);
}

Each level of the call takes one argument at a time off the back of the tuple using std::get and the template Index parameter, decrements the index, and recurses. This is a bit hard to imagine, so I’ll illustrate. This sequence is not meant to be taken too literally.

Let’s say I have a tuple of string, int, char, and double. I’ll denote this example tuple as tuple("hello", 3, 'c', 2.0). The expansion would happen something like the following

expand(f, tuple("hello", 3, 'c', 2.0)) 
-> call<4>(f, tuple("hello", 3, 'c', 2.0))
-> call<3>(f, tuple("hello", 3, 'c', 2.0), 2.0)
-> call<2>(f, tuple("hello", 3, 'c', 2.0), 'c', 2.0)
-> call<1>(f, tuple("hello", 3, 'c', 2.0), 3, 'c', 2.0)
-> call<0>(f, tuple("hello", 3, 'c', 2.0), "hello", 3, 'c', 2.0)
-> f("hello", 3, 'c', 2.0)

Of course std::integer_sequence in C++14 turns all of this on its head. Maybe I should’ve implemented that instead…