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

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

是否可以测试某些类型是否可以通过 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 以将其绑定到另一个 class 模板。 使用 TestValidBind<template template type>::toTypesOf<typename> 我想测试是否可以将给定类型的参数绑定到 template template parameter 并附加一个附加类型(float 在示例中)。

我想要的是 TestValidBind<TwoParamStruct>::toTypesOf<tmp>::typetrue_typeTestValidBind<OneParamStruct>::toTypesOf<tmp>::typefalse_type.


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

../test_SFINAE_with_template_binding.cc: In function ‘int main()’: ../test_SFINAE_with_template_binding.cc:34:96: error: ‘TestValidBind<OneParamStruct>::toTypesOf<TemporaryBindObject<int> >::type’ has not been declared

如果 OneParamStruct 行被删除,

并报告 false_type(这是错误的)。

使用 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++。

DEMO

如果您愿意,我们可以更进一步,将所有内容放入 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> >;

Live example.

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

这里我们问"can we apply"rebind< ???, ztag<Dest> >。这是 Dest<???>.

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

得到 types<Ts..., void>

rebind 采用带有模板参数的类型和另一个模板的 ztag,并将第一个参数模板参数应用于 ztag.[=32= 中的模板]

can_apply询问隐含模板应用是否合法。如果我更一致,can_apply 也会将 ztag 作为其第一个参数。

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