这更多的是一个概念性问题。我试图找到将两个参数模板(参数是类型)转换为一个参数模板的最简单方法。即绑定其中一种类型。
这将是相当于 boost/std 中的
bind
的元编程。我的示例包括一个可能的用例,即将 std::is_same
作为模板参数传递给采用单参数模板模板参数(std::is_same
是两个参数模板)的模板,即传递给 TypeList::FindIf
。 TypeList
在这里没有完全实现,FindIf
也没有完全实现,但你明白了。它采用“一元谓词”并返回该谓词为真的类型,如果不是这样的类型则返回 void
。
我有 2 个工作变体,但第一个不是单行代码,第二个使用相当冗长的
BindFirst
装置,这不适用于非类型模板参数。有没有一种简单的方法可以写这样的一行字?我相信我正在寻找的程序称为 currying
。
#include <iostream>
template<template<typename, typename> class Function, typename FirstArg>
struct BindFirst
{
template<typename SecondArg>
using Result = Function<FirstArg, SecondArg>;
};
//template<typename Type> using IsInt = BindFirst<_EqualTypes, int>::Result<Type>;
template<typename Type> using IsInt = std::is_same<int, Type>;
struct TypeList
{
template<template<typename> class Predicate>
struct FindIf
{
// this needs to be implemented, return void for now
typedef void Result;
};
};
int main()
{
static_assert(IsInt<int>::value, "");
static_assert(!IsInt<float>::value, "");
// variant #1: using the predefined parameterized type alias as predicate
typedef TypeList::FindIf<IsInt>::Result Result1;
// variant #2: one-liner, using BindFirst and std::is_same directly
typedef TypeList::FindIf< BindFirst<std::is_same, int>::Result>::Result Result2;
// variant #3: one-liner, using currying?
//typedef TypeList::FindIf<std::is_same<int, _>>::Result Result2;
return 0;
}
点击这里查看在线编译器GodBolt中的代码。
我认为执行此操作的典型方法是将所有内容保留在类型世界中。不要采用模板模板——它们很混乱。让我们编写一个名为
ApplyAnInt
的元函数,它将采用“元函数类”并将 int
应用于它:
template <typename Func>
struct ApplyAnInt {
using type = typename Func::template apply<int>;
};
一个简单的元函数类可能只是检查给定类型是否为
int
:
struct IsInt {
template <typename T>
using apply = std::is_same<T, int>;
};
static_assert(ApplyAnInt<IsInt>::type::value, "");
现在的目标是支持:
static_assert(ApplyAnInt<std::is_same<_, int>>::type::value, "");
我们可以做到。我们将调用包含
_
“lambda 表达式”的类型,并编写一个名为 lambda
的元函数,它将转发不是 lambda 表达式的元函数类,或者生成一个新的元函数(如果是):
template <typename T, typename = void>
struct lambda {
using type = T;
};
template <typename T>
struct lambda<T, std::enable_if_t<is_lambda_expr<T>::value>>
{
struct type {
template <typename U>
using apply = typename apply_lambda<T, U>::type;
};
};
template <typename T>
using lambda_t = typename lambda<T>::type;
所以我们更新了原来的元函数:
template <typename Func>
struct ApplyAnInt
{
using type = typename lambda_t<Func>::template apply<int>;
};
现在剩下两件事:我们需要
is_lambda_expr
和 apply_lambda
。这些实际上并没有那么糟糕。对于前者,我们将查看它是否是类模板的实例化,其中类型之一是 _
:
template <typename T>
struct is_lambda_expr : std::false_type { };
template <template <typename...> class C, typename... Ts>
struct is_lambda_expr<C<Ts...>> : contains_type<_, Ts...> { };
对于
apply_lambda
,我们只需将 _
替换为给定类型:
template <typename T, typename U>
struct apply_lambda;
template <template <typename...> class C, typename... Ts, typename U>
struct apply_lambda<C<Ts...>, U> {
using type = typename C<std::conditional_t<std::is_same<Ts, _>::value, U, Ts>...>::type;
};
这就是您实际上所需要的。我会将其扩展以支持
arg_<N>
,作为读者的练习。
是的,我有这个问题。经过几次迭代才找到一个合适的方法来做到这一点。基本上,要做到这一点,我们需要指定我们想要和需要的合理表示。我借用了
std::bind()
的一些方面,因为我想指定我想要绑定的模板以及我想要绑定到它的参数。然后,在该类型中,应该有一个模板,允许您传递一组类型。
所以我们的界面将如下所示:
template <template <typename...> class OP, typename...Ts>
struct tbind;
现在我们的实现将包含这些参数以及最后将应用的类型容器:
template <template <typename...> class OP, typename PARAMS, typename...Ts>
struct tbind_impl;
我们的基本情况将为我们提供一个模板类型,我将其称为
ttype
,它将返回所包含类型的模板:
template <template <typename...> class OP, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>>
{
template<typename...Us>
using ttype = OP<Ss...>;
};
然后我们有将下一个类型移动到容器中并让
ttype
引用稍微简单的基本情况中的 ttype
的情况:
template <template <typename...> class OP, typename T, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, T, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, std::tuple<Ss..., T>
, Ts...
>::template ttype<Us...>;
};
最后,我们需要重新映射将传递给
ttype
的模板:
template <template <typename...> class OP, size_t I, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, std::integral_constant<size_t, I>, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, typename std::tuple<
Ss...
, typename std::tuple_element<
I
, typename std::tuple<Us...>
>::type
>
, Ts...
>::template ttype<Us...>;
现在,由于程序员很懒,并且不想为每个要重新映射的参数键入
std::integral_constant<size_t, N>
,因此我们指定一些别名:
using t0 = std::integral_constant<size_t, 0>;
using t1 = std::integral_constant<size_t, 1>;
using t2 = std::integral_constant<size_t, 2>;
...
哦,差点忘了我们接口的实现了:
template <template <typename...> class OP, typename...Ts>
struct tbind : detail::tbind_impl<OP, std::tuple<>, Ts...>
{};
请注意,
tbind_impl
被放置在 detail
命名空间中。
瞧,
tbind
!
不幸的是,c++17之前有一个缺陷。如果您将
tbind<parms>::ttype
传递给需要具有特定数量参数的模板的模板,您将收到错误,因为参数数量不匹配(特定数量与任何数量都不匹配)。这使事情稍微复杂化,需要额外的间接级别。 :(
template <template <typename...> class OP, size_t N>
struct any_to_specific;
template <template <typename...> class OP>
struct any_to_specific<OP, 1>
{
template <typename T0>
using ttype = OP<T0>;
};
template <template <typename...> class OP>
struct any_to_specific<OP, 2>
{
template <typename T0, typename T1>
using ttype = OP<T0, T1>;
};
...
使用它来包装
tbind
将强制编译器识别具有指定数量参数的模板。
使用示例:
static_assert(!tbind<std::is_same, float, t0>::ttype<int>::value, "failed");
static_assert( tbind<std::is_same, int , t0>::ttype<int>::value, "failed");
static_assert(!any_to_specific<
tbind<std::is_same, float, t0>::ttype
, 1
>::ttype<int>::value, "failed");
static_assert( any_to_specific<
tbind<std::is_same, int , t0>::ttype
, 1
>::ttype<int>::value, "failed");
所有这些都成功了。
柯里化:
template <template <class...> class T, class... Head>
struct curry
{
template <class... Tail>
using type = T<Head..., Tail...>;
};
static_assert(curry<std::is_same, int>::type<int>); // true
static_assert(curry<std::is_same, int>::type<float>); // false