C++如何生成所有函数重载的排列组合?

18

假设我有类 Date,以及类 YearMonthDay

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
2个回答

40

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

编辑: 如果你也允许默认参数,你可以这样做:

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

实时演示
带有无效构造函数调用的实时演示


谢谢,这是我正在寻找的优雅解决方案 :) - lukas.pukenis
2
@Gerard:通过实现一个 get_or_default 方法是可以实现的。 - Jarod42
@Gerard:编辑后加入了默认参数和在线演示 - Jarod42
在您的实时演示中,Date(printf, 3.14, "hello") 生成默认日期。我认为检查传递的类型仅来自集合 年、月、日 可能是个好主意。 ;) (并且每种类型最多只使用一次) - Yakk - Adam Nevraumont
@Yakk:没错,我会加上检查的。(重复项已经由std::get<T>管理了)。 - Jarod42

6
在C++14中,取3个通用参数,将它们转发到tuple中,将该tuple转发到一个新的构造函数(可能带有标记类型以帮助分派),并使用基于类型的std::get来提取每个类型。将其转发到另一个构造函数,带有标记以帮助调度。
使用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>的函数。
检查y/m/d是否都存在的SFINAE可能比较困难,但也许不是必须的。
如果您的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> 可能是更好的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接