Reputation: 705
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
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
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
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
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
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