dudu
dudu

Reputation: 705

All combinations of constructor arguments

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?

Upvotes: 3

Views: 697

Answers (5)

Jarod42
Jarod42

Reputation: 217358

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;
}

Upvotes: 1

David Neiss
David Neiss

Reputation: 8237

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.

Upvotes: 3

Adrian Shum
Adrian Shum

Reputation: 40036

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();

Upvotes: 0

godel9
godel9

Reputation: 7390

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?

Upvotes: 4

c-smile
c-smile

Reputation: 27460

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.

Upvotes: 3

Related Questions