C++如何生成函数重载的所有排列?
C++ how to generate all the permutations of function overloads?
假设我有 类 Date
和 类 Year
、Month
和 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;
}
这让我不必为 Date
设置特定的参数布局,因为我有很多重载。
我可以自动生成所有 permutations/overloadings 吗?
明确一点:
- 排列只是参数布局,与它们无关
据我所知,改变是不可能自动化的。
- 所有生成的重载都应该有相同的代码
参数的布局不会改变逻辑本身。
使用 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;
};
在 C++14 中,采用 3 个泛型参数,将它们转发给一个元组,将该元组转发给一个新的构造函数(可能带有标记类型以帮助分派),并使用基于类型的 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>
.
等价物
SFINAE 检查 y/m/d 是否全部存在更难,但也许不需要。
优化(添加 move/perfect 转发)是另一项改进,如果您的 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>
可能是一个更好的主意。
假设我有 类 Date
和 类 Year
、Month
和 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;
}
这让我不必为 Date
设置特定的参数布局,因为我有很多重载。
我可以自动生成所有 permutations/overloadings 吗?
明确一点:
- 排列只是参数布局,与它们无关 据我所知,改变是不可能自动化的。
- 所有生成的重载都应该有相同的代码 参数的布局不会改变逻辑本身。
使用 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;
};
在 C++14 中,采用 3 个泛型参数,将它们转发给一个元组,将该元组转发给一个新的构造函数(可能带有标记类型以帮助分派),并使用基于类型的 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>
.
SFINAE 检查 y/m/d 是否全部存在更难,但也许不需要。
优化(添加 move/perfect 转发)是另一项改进,如果您的 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>
可能是一个更好的主意。