18

Lets say I have classes Date and classes Year, Month and Day.

struct Date {
  Date(Year year, Month month, Day day) : d(day), m(month), y(year) {};
  Date(Month month, Day day, Year year) : d(day), m(month), y(year) {};
  Date(Day day, Month month, Year year) : d(day), m(month), y(year) {};
  Date(Day day, Year year, Month month) : d(day), m(month), y(year) {};
  ...
  ...

  private:
    Day d;
    Month m;
    Year y;
}

This allows me not to have a specific layout of arguments for Date as I have a lot of overloadings.

Am I able to generate all the permutations/overloadings automatically?

Just to be clear:

  • Permutations are only of argument layout, nothing about them should change as I know that would not be possible to automate.
  • All the generated overloadings should have the same code as only the layout of arguments changes not the logic itself.
Jarod42
  • 203,559
  • 14
  • 181
  • 302
lukas.pukenis
  • 13,057
  • 12
  • 47
  • 81
  • 2
    This can be done, but I am going to argue that this shouldn't be done. With the exception of overloading an operator whose operations should be commutative, you really shouldn't train your users into thinking that argument order doesn't matter. This can only end in tears. – KevinZ Jun 06 '15 at 14:16

2 Answers2

40

With C++14, you may do:

struct Date {
public:
    Date(const Year& year, const Month& month, const Day& day) :
        d(day), m(month), y(year)
    {}

    template <typename T1, typename T2, typename T3>
    Date(const T1& t1, const T2& t2, const T3& t3) : 
        Date(std::get<Year>(std::tie(t1, t2, t3)),
             std::get<Month>(std::tie(t1, t2, t3)),
             std::get<Day>(std::tie(t1, t2, t3)))
    {}

private:
    Day d;
    Month m;
    Year y;
};

Edit: if you would also allow default argument, you may do something like:

namespace detail
{
    template <typename T, typename... Ts> struct has_T;

    template <typename T> struct has_T<T> : std::false_type {};

    template <typename T, typename... Ts> struct has_T<T, T, Ts...>
    : std::true_type {};

    template <typename T, typename Tail, typename... Ts>
    struct has_T<T, Tail, Ts...> : has_T<T, Ts...> {};

    template <typename T, typename... Ts>
    const T& get_or_default_impl(std::true_type,
                                 const std::tuple<Ts...>& t,
                                 const T&)
    {
        return std::get<T>(t);
    }

    template <typename T, typename... Ts>
    const T& get_or_default_impl(std::false_type,
                                 const std::tuple<Ts...>&,
                                 const T& default_value)
    {
        return default_value;
    }

    template <typename T1, typename T2> struct is_included;

    template <typename... Ts>
    struct is_included<std::tuple<>, std::tuple<Ts...>> : std::true_type {};

    template <typename T, typename... Ts, typename ... Ts2>
    struct is_included<std::tuple<T, Ts...>, std::tuple<Ts2...>> :
        std::conditional_t<has_T<T, Ts2...>::value,
                          is_included<std::tuple<Ts...>, std::tuple<Ts2...>>,
                          std::false_type> {};

}

template <typename T, typename... Ts>
const T& get_or_default(const std::tuple<Ts...>& t,
                        const T& default_value = T{})
{
    return detail::get_or_default_impl<T>(detail::has_T<T, Ts...>{}, t, default_value);
}

And then

struct Date {
public:
    Date(const Year& year, const Month& month, const Day& day) :
        d(day), m(month), y(year)
    {}

    template <typename ... Ts,
              typename std::enable_if_t<
                  detail::is_included<std::tuple<Ts...>,
                  std::tuple<Year, Month, Day>>::value>* = nullptr>
    Date(const Ts&... ts) :
        Date(get_or_default<const Year&>(std::tie(ts...)),
             get_or_default<const Month&>(std::tie(ts...)),
             get_or_default<const Day&>(std::tie(ts...)))
    {}

private:
    Day d;
    Month m;
    Year y;
};

Live Demo
Live Demo with invalid constructor call

Jarod42
  • 203,559
  • 14
  • 181
  • 302
6

In C++14, take 3 generic arguments, forward them to a tuple, forward that tuple to a new constructor (possibly with a tag type to aid dispatch), and use the type-based std::get to exctract each type. Forward that to yet another constructor, with a tag to aid in dispatchimg.

SFINAE checks to provide for early failure optional.

struct Date {
private:
  struct as_tuple{};
  struct in_order{}; 
public:
  template<class A,class B,class C,
    // SFINAE test based on type_index below:
    class=decltype(
      type_index<Year,A,B,C>{}+type_index<Month,A,B,C>{}+type_index<Day,A,B,C>{}
    )
  >
  Date(A a,B b,C c):
    Date(as_tuple{},
      std::make_tuple(std::move(a),std::move(b),std::move(c))
    )
  {}
private:
  template<class...Ts>
  Date(as_tuple, std::tuple<Ts...> t):
    Date(in_order{},
      std::get<Year>(t),std::get<Month>(t),std::get<Day>(t)
    )
  {}
  Date(in_order,Year y_,Month m_,Day d_):
    y(y_),m(m_),d(d_)
  {}
};

In C++11, you can implement your own equivalent of std::get<T>.

SFINAE checks that y/m/d are all present are harder, but maybe not needed.

Optimization (adding move/perfect forwarding) is another improvement that may not be needed if your y/m/d types are simple enough.

The technique of forwarding constructors and tags is based on the idea of doing one thing at a time, instead of all at once. The code is going to be already strange enough.

Implementing your own std::get<T> is easy. Making it SFINAE friendly a bit harder:

 // helpers to keep code clean:
 template<std::size_t n>
 using size=std::integral_constant<std::size_t, n>;
 template<class T>struct tag{using type=T;};

 template<class T, class...Ts>
 struct type_index_t{}; // SFINAE failure

 // client code uses this.  Everything else can go in namespace details:
 template<class T, class...Ts>
 using type_index = typename type_index_t<T,Ts...>::type;

 // found a match!
 template<class T, class...Ts>
 struct type_index_t<T, T, Ts...>:
   tag<size<0>>
 {};
 template<class T, class T0, class...Ts>
 struct type_index_t<T, T0, Ts...>:
   tag<size<type_index<T,Ts...>::value+1>>
 {};

// SFINAE (hopefully) std::get<T>:
template<class T, class...Ts>
auto my_get( std::tuple<Ts...>& tup )
-> decltype( std::get< type_index<T,Ts...>::value >(tup) ) {
  return std::get< type_index<T,Ts...>::value >(tup);
}
template<class T, class...Ts>
auto my_get( std::tuple<Ts...> const& tup )
-> decltype( std::get< type_index<T,Ts...>::value >(tup) ) {
  return std::get< type_index<T,Ts...>::value >(tup);
}
template<class T, class...Ts>
auto my_get( std::tuple<Ts...>&& tup )
-> decltype( std::get< type_index<T,Ts...>::value >(std::move(tup)) ) {
  return std::get< type_index<T,Ts...>::value >(std::move(tup));
}

but that is just an untested sketch. Looking at the proposals for C++14 std::get<Type> is probably a better idea.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524