3

Very likely a dumb question, but here it goes anyways.

Is there a short way of declaring constructors that would take any combination of arguments? For instance, a ctor with 3 args has 2^3 possible combinations (as seen in the mockup below).

template<typename T>
struct Node{
    Node(Species sp, Edge edge, T data) : sp_m(sp), edge_m(edge), data_m(data) { }

    // Default Ctor, 0 arg
    Node() : Node(Species(), Edge(), T()) { }

    // All combinations of 1 arg
    Node(Species sp): Node(sp,          Edge(), T())  { }
    Node(Edge edge) : Node(Species(),   edge,   T())  { }
    Node(T data)    : Node(Species(),   Edge(), data) { }

    // All combinations of 2 args
    Node(Species sp, Edge edge) : Node(sp,        edge,   T()) { }
    Node(Species sp, T data)    : Node(sp,        Edge(), data){ }
    Node(Edge edge, T data)     : Node(Species(), edge,   data){ }

    Species sp_m;
    Edge    edge_m;
    T       data_m;
};

Can I avoid declaring all of the different ctors?

dudu
  • 675
  • 6
  • 15

5 Answers5

4

You can use default parameters:

template<typename T>
struct Node{
    explicit Node(Species sp = Species(), Edge edge = Edge(), T data = T()) :
        sp_m(sp), edge_m(edge), data_m(data) { }

    Species sp_m;
    Edge    edge_m;
    T       data_m;
};

Note the use of explicit to prevent use of the constructor as an implied conversion operator. (See this link for more information.)

You can instantiate Node objects with any of the following patterns:

int main()
{
    Species s;
    Edge e;
    int i;

    Node<int> x1 = Node<int>();

    Node<int> x2 = Node<int>(s);
    Node<int> x3 = Node<int>(s, e);
    Node<int> x4 = Node<int>(s, e, i);

    // Can't skip parameters, so supply default parameters when needed:
    Node<int> x5 = Node<int>(Species(), Edge(), i);
}

You can't do the following:

Node<int> x6 = Node<int>(i);

That's probably okay, though, since you'd otherwise have ambiguity in which constructor to call in the following case:

Node<Species> x6 = Node<Species>(s);

Is s specifying the sp parameter or the data parameter?

Community
  • 1
  • 1
godel9
  • 7,340
  • 1
  • 33
  • 53
3

I would do it not by constructor but by overloading, say, operator <<.

struct Node {

   Node() : Node(Species(), Edge(), T()) { }

   Node& operator << (Species sp) {...}
   Node& operator << (Edge edge) { ... }
   Node& operator << (T data) { ... }

}

And to use it in any combination:

Node n1; n1 << species << edge;
Node n2; n2 << edge;
etc.
c-smile
  • 26,734
  • 7
  • 59
  • 86
3

Yet another option - use the Boost Parameter library to used named parameters along with defaults so you can specify 1, 2, or 3 parameters. One advantage of this approach is that you can provide them in any order. See http://www.boost.org/doc/libs/1_55_0/libs/parameter/doc/html/index.html for more info.

David Neiss
  • 8,161
  • 2
  • 20
  • 21
  • +1, although getting some more details right here in the answer would be great. – jogojapan Jan 02 '14 at 04:24
  • Thanks, see http://www.boost.org/doc/libs/1_55_0/libs/parameter/doc/html/index.html#parameter-enabled-constructors – David Neiss Jan 02 '14 at 04:37
  • Well, by *right here* I mean *right here*. Not in a remote location. Also it would be great to see a concrete (and complete) example that actually relates to the code in the question. – jogojapan Jan 02 '14 at 04:48
  • @DavidNeiss I second jogojapan here. Would be great to see an example based on the question here... – dudu Jan 02 '14 at 13:57
1

An other method using SFINAE.
Bonus: all argument orders are fine.
Same restriction on T: cannot be neither Specie nor Edge.

#include <tuple>

// count the number of T in Ts...
template <typename T, typename ...Ts> struct count_type;

template <typename T, typename Tail, typename ...Ts>
struct count_type<T, Tail, Ts...>
{
    constexpr static int value = std::is_same<T, Tail>::value + count_type<T, Ts...>::value;
};
template <typename T> struct count_type<T> { constexpr static int value = 0; };

// index of T in Ts..., or -1 if not found
template <typename T, typename ... Ts> struct get_index;

template <typename T> struct get_index<T> { static const int value = -1; };

template <typename T, typename ... Ts> struct get_index<T, T, Ts...> { static const int value = 0; };

template <typename T, typename Tail, typename ... Ts>
struct get_index<T, Tail, Ts...>
{
    static const int value =
        get_index<T, Ts...>::value == -1 ? -1 : 1 + get_index<T, Ts...>::value;
};

// similar to get<T>(tuple), but return T{} if not found
template <typename T, int N, typename ... Ts>
struct get_helper
{
    static T get(const std::tuple<const Ts&...>& t) { return std::get<N>(t); }
};

template <typename T, typename ... Ts>
struct get_helper<T, -1, Ts...>
{
    static T get(const std::tuple<const Ts&...>& t) { return T{}; }
};

// similar to get<T>(tuple), but return T{} if not found
template <typename T, typename ... Ts>
T get_or_construct(const std::tuple<const Ts&...>& t)
{
    return get_helper<T, get_index<T, Ts...>::value, Ts...>::get(t);
}


class Species {};
class Edge {};

template<typename T>
struct Node{
    Node(const Species& sp, const Edge& edge, T data) : sp_m(sp), edge_m(edge), data_m(data) { }

    template <typename ... Ts>
    Node(const Ts&... ts) : Node(std::tie(ts...)) {}

private:
    template <typename ... Ts, typename =
        typename std::enable_if<count_type<Species, Ts...>::value <= 1
                                && count_type<Edge, Ts...>::value <= 1
                                && count_type<T, Ts...>::value <= 1>::type>
    Node(const std::tuple<const Ts&...>& t) :
        Node(get_or_construct<Species>(t),
             get_or_construct<Edge>(t),
             get_or_construct<T>(t))
    {}

private:
    Species sp_m;
    Edge    edge_m;
    T       data_m;
};

int main(int argc, char *argv[])
{
    Node<int> n(Species {}, Edge{}, 42); // normal constructor
    Node<int> n2(Species {}, 42);        // template constructor
    return 0;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • +1. Thx, I wasn't aware of this pattern. It will take me a while to digest it though. – dudu Jan 02 '14 at 13:54
0

You may consider something like a fluent builder. (Here I omitted use of template to simplify the picture but I think it should be trivial for you to add template parameter in the builder by yourself:

(psuedo code)

// Have only 1 ctor for Node, or simply no ctor as it is a struct

struct Node {
   Species m_sp;
   Edge    m_edge;
   Data    m_data;
   Node(Species& sp, Edge& edge, Data& data) : m_sp(sp), m_edge(edge), m_data(data) { }
};

class NodeBuilder {
private:
  Species* m_species;
  Edge* m_edge;
  Data* m_data;

public:
  static NodeBuilder& newBuilder() {
    return NodeBuilder();
  }

  void withSpecies(Species& species) {
    m_species= &species;
  }
  void withEdge(Edge& edge) {
    m_edge = &edge;
  }
  void withData(Data& data) {
    m_data = data;
  }
  void Node& toNode() {
    return Node(m_species? (*m_species) : Species(), 
                m_edge? (*edge) : Edge(),
                m_data? (*data) : Data());
  }
};

So your construction code should look like:

Node node = NodeBuilder.newBuilder()
              .withSpices(someSpices)
              .withData(someData)
              .toNode();
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131