使用SFINAE检查类型是否可以绑定到模板模板参数

Use SFINAE to check if types can be bound to template template parameter

本文关键字:绑定 参数 SFINAE 检查 类型 是否 使用      更新时间:2023-10-16

是否可以测试某些类型是否可以通过SFINAE绑定到模板模板参数?

我认为我尝试做的最好用以下示例代码来解释:

#include <iostream>
template<typename... T> using void_t = void;
template<typename T> struct TemporaryBindObject
{
    using type = TemporaryBindObject<T>;
};
template<template<typename...> class Dest> struct TestValidBind
{
    template<typename... Ts> struct toTypesOf
    {
        using type = std::false_type;
    };
    template<template<typename...> class Src, typename... Ts> struct toTypesOf<Src<Ts...>, void_t<Dest<Ts...,float>>>
    {
        using type = std::true_type;
    };
};
template<typename T> struct OneParamStruct{};
template<typename T1, typename T2> struct TwoParamStruct{};
int main()
{
    using tmp = TemporaryBindObject<int>;
    std::cout << "Can bind to TwoParamStruct: " << TestValidBind<TwoParamStruct>::toTypesOf<tmp>::type::value << std::endl;
    std::cout << "Can bind to OneParamStruct: " << TestValidBind<OneParamStruct>::toTypesOf<tmp>::type::value << std::endl;
}

首先,我制作了一个临时类型tmp,我想从中获取模板参数int,将其绑定到另一个类模板。使用TestValidBind<template template type>::toTypesOf<typename>,我想测试是否可以将给定类型的参数绑定到template template parameter,并附加一个附加类型(示例中为float)。

我想要的是TestValidBind<TwoParamStruct>::toTypesOf<tmp>::typetrue_type,而TestValidBind<OneParamStruct>::toTypesOf<tmp>::typefalse_type


代码示例未使用g++ -std=c++11(5.3.1)编译,出现以下错误:

/test_SFINAE_with_template_binding.cc:在函数"int main()"中:../test_SFINAE_with_template_binding。cc:34:96:错误:'测试有效绑定<OneParamStruct>::到类型Of<TemporaryBindObject<int>>:类型"尚未声明为

并且如果CCD_ 12线被移除则报告CCD_。

对于clang++ -std=c++11(3.8.0),代码会编译,但在这两种情况下都会报告false_type

这样的事情有可能发生吗?


编辑:将附加类型从void更改为float,以突出显示我想检查附加类型是否可行。

void_t技巧要求我们给主模板的一个参数一个默认类型void。您不需要主模板(toTypesOf)是可变的。

bool类型的特征从false_typetrue_type继承而不是具有嵌套的type更为惯用。TemporaryBindObject不需要具有嵌套的type

TestValidBind应该看起来像这样:

template<template<typename...> class Dest> struct TestValidBind
{
    template<template<typename...> class Dest, typename T, typename = void_t<>> struct toTypesOf
        : std::false_type
    {};
    template<template<typename...> class Dest, template<typename...> class Src, typename... Ts> struct toTypesOf<Dest, Src<Ts...>, void_t<Dest<Ts..., float>>>
        : std::true_type
    {};
};

编辑:正如你所说,这对g++不起作用,我不知道为什么。但我们可以简化它,看看这是否有帮助。我们实际上并不需要封闭结构TestValidBind。如果我们将Dest参数移植到toTypesOf模板中,则该模板可以在名称空间范围内:

template<template<typename...> class Dest, typename T, typename = void_t<>> struct toTypesOf
    : std::false_type
{};
template<template<typename...> class Dest, template<typename...> class Src, typename... Ts> struct toTypesOf<Dest, Src<Ts...>, void_t<Dest<Ts..., float>>>
    : std::true_type
{};

这在g++中有效。

演示

如果您愿意,我们可以更进一步,通过将所有内容放入detail名称空间并将其封装在别名模板中,使其更易于使用:

namespace detail
{
    template<typename... T> using void_t = void;
    template<typename... T> struct TemporaryBindObject {};
    template<template<typename...> class Dest, typename T, typename = void_t<>> struct toTypesOf
        : std::false_type {};
    template<template<typename...> class Dest, template<typename...> class Src, typename... Ts> struct toTypesOf<Dest, Src<Ts...>, void_t<Dest<Ts...>>>
        : std::true_type {};
}
template<template<typename...> class Dest, typename... Ts>
using IsValidBind = typename detail::toTypesOf<Dest, detail::TemporaryBindObject<Ts...>>;
template<template<typename...> class Dest, typename... Ts>
using IsValidBindWithFloat = IsValidBind<Dest, Ts..., float>;
template<template<typename...> class Dest, typename... Ts>
using IsValidBindWithVoid = IsValidBind<Dest, Ts..., void>;
std::cout << "Can bind to TwoParamStruct: " << IsValidBindWithFloat<TwoParamStruct, int>::value << std::endl;
std::cout << "Can bind to OneParamStruct: " << IsValidBindWithFloat<OneParamStruct, int>::value << std::endl;

现在我们不需要using tmp,我们有了一个更通用的解决方案,您可以轻松地更改要用作附加类型的类型。

DEMO

以下是我的操作方法:

using std::void_t; // or write your own
template<class T>struct tag{using type=T;};
template<template<class...>class Z>struct ztag{
  template<class...Ts>using result=Z<Ts...>;
};
namespace details {
  template<class Src, class Target, class=void>
  struct rebind {};
  template<template<class...>class Src, template<class...>class Target, class...Ts>
  struct rebind<Src<Ts...>, ztag<Target>, void_t<Target<Ts...>>>:
    tag<Target<Ts...>>
  {};
}
template<class Src, class zDest>
using rebind = typename details::rebind<Src,zDest>::type;
namespace details {
  template<template<class...>class Z, class, class...Ts>
  struct can_apply : std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...> : std::true_type {};
}
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply<Z, void, Ts...>::type;
template<class...>struct types{using type=types;};
namespace details {
  template<class types, class...Us>
  struct append;
  template<class...Ts, class...Us>
  struct append<types<Ts...>, Us...>:
    types<Ts..., Us...>
  {};
}
template<class types, class...Us>
using append = typename details::append<types, Us...>::type;
template<class Src, template<class...>class Dest>
using can_rebind_with_void =
  can_apply< rebind, append< rebind<Src, ztag<types>>, void >, ztag<Dest> >;

活生生的例子。

ztag是因为当只处理类型时,元编程要容易得多。ztag获取一个模板并将其转换为一个类型。

zapply然后应用它:

namespace details {
  template<class Z, class...Ts>
  struct zapply {};
  template<template<class...>class Z, class...Ts>
  struct zapply<ztag<Z>, Ts...>:
    tag<Z<Ts...>>
  {};
}
template<class Z, class...Ts>
using zapply = typename details::zapply<Z,Ts...>::type;

在任何情况下,除了一行解决方案:之外,所有东西都是通用的

template<class Src, template<class...>class Dest>
using can_rebind_with_void =
  can_apply< rebind, append< rebind<Src, ztag<types>>, void >, ztag<Dest> >;

在这里,我们询问"我们可以申请"rebind< ???, ztag<Dest> >。这将执行Dest<???>

???Src<???>开始,将其类型移到types<???>,并附加一个void。所以我们从Src<Ts...>得到types<Ts..., void>

rebind采用一个带有模板args的类型,以及另一个模板的ztag,并将第一个参数模板args应用于ztag内的模板。

can_apply询问隐含模板应用程序是否合法。如果我更一致的话,can_apply也会把ztag作为它的第一个自变量。

我用ztag做这一切的原因是因为模板元编程更流畅,因为一切都是类型。