2015-05-31 2 views
18

Допустим, у меня есть классы Date и классы Year, Month и Day.C++ как сгенерировать все перестановки перегрузок функций?

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

Это позволяет мне не иметь конкретный формат аргументы для Date, как у меня есть много перегрузок.

Могу ли я автоматически сгенерировать все перестановки/перегрузки?

Просто чтобы быть ясно:

  • Перестановки не только расположение аргументов, ничего о них должны изменения, как я знаю, что было бы невозможно автоматизировать.
  • Все сгенерированные перегрузки должны иметь тот же код, что и макет аргументов , а не сама логика.
+2

Это можно сделать, но я собираюсь утверждать, что этого не должно быть сделано. За исключением перегрузки оператора, операции которого должны быть коммутативными, вы действительно не должны обучать своих пользователей думать, что порядок аргументов не имеет значения. Это может закончиться только слезами. – KevinZ

ответ

38

С C++ 14, вы можете сделать:

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: если вы также позволит аргумент по умолчанию, вы можете сделать что-то вроде:

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

И затем

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

+0

Спасибо, это изящное решение, которое я искал :) –

+0

Не могли бы вы также выполнить эту работу с аргументами по умолчанию? – Gerard

+2

@Gerard: это было бы возможно путем реализации 'get_or_default'. – Jarod42

6

В C++ 14, принимают 3 общие аргументы, направить их в кортеж, направить этот кортеж нового конструктора (возможно с типом тега, чтобы помочь отправки), а также использовать тип на основе std::get, чтобы вырезать каждый тип. Перейдите к еще одному конструктору с тегом, чтобы помочь в dispatchimg.

SFINAE проверяет, чтобы досрочный отказ был опциональным.

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_) 
    {} 
}; 

В C++ 11, вы можете реализовать свой собственный эквивалент std::get<T>.

SFINAE проверяет, что y/m/d все присутствующие сложнее, но, возможно, не нужны.

Оптимизация (добавление переадресации/совершенная переадресация) - это еще одно улучшение, которое может не понадобиться, если ваши типы y/m/d достаточно просты.

Техника переадресации конструкторов и тегов основана на идее делать одну вещь за раз, а не сразу. Код будет довольно странным.

Реализация собственных std::get<T> проста. Сделать его SFINAE дружественным немного сложнее:

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

но это всего лишь непроверенный эскиз. Рассмотрение предложений по C++ 14 std::get<Type>, вероятно, является лучшей идеей.

 Смежные вопросы

  • Нет связанных вопросов^_^