【问题标题】:Use SFINAE to check if types can be bound to template template parameter使用 SFINAE 检查类型是否可以绑定到模板模板参数
【发布时间】:2016-04-04 19:46:49
【问题描述】:

是否可以测试某些类型是否可以通过 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&lt;template template type&gt;::toTypesOf&lt;typename&gt;,我想测试是否可以将给定类型的参数绑定到template template parameter 并附加一个附加类型(示例中为float)。

我想要的是TestValidBind&lt;TwoParamStruct&gt;::toTypesOf&lt;tmp&gt;::typetrue_typeTestValidBind&lt;OneParamStruct&gt;::toTypesOf&lt;tmp&gt;::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:错误: 'TestValidBind::toTypesOf >::type' 没有被声明

如果删除了OneParamStruct 行,则报告false_type(这是错误的)。

使用clang++ -std=c++11 (3.8.0) 代码编译但在两种情况下都报告false_type

这样的事情可能吗?


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

【问题讨论】:

    标签: c++ templates c++11 metaprogramming


    【解决方案1】:

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

    bool 类型 trait 从 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 模板中,则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

    【讨论】:

    • 感谢您的有效更正。实际上,它并没有完全回答我的问题。我要求的相反仍然不起作用(可能我的例子没有很好地突出这一点)。我想尝试的是添加一个额外的类型(我选择了void)。我会将其更改为其他内容以突出这一点。
    • 我的评论有点太快了:我的示例(类型太多)以及您的更正现在适用于 clang,但仍然无法使用 g++5。您的示例适用于两种编译器。
    • 我将调整我的问题以适合您的答案(因为您确实修正了一个愚蠢的错误)并在明天提出一个新问题。但请注意,我并没有通过编辑改变我的问题!
    • 好吧,我不知道为什么 g++ 不喜欢那样。我错过了“附加其他类型”的描述;我专注于代码。
    • 现在有了第二个答案,我无法更改我的问题...另一个答案适用于 gcc,我仍然需要检查实际差异在哪里。
    【解决方案2】:

    我会这样做:

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

    在这里我们问“我们可以申请吗”rebind&lt; ???, ztag&lt;Dest&gt; &gt;。这是一个Dest&lt;???&gt;

    ???Src&lt;???&gt; 开头,将其类型移至types&lt;???&gt;,附加一个void。所以我们从Src&lt;Ts...&gt; 得到types&lt;Ts..., void&gt;

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

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

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

    【讨论】:

      猜你喜欢
      • 2017-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-15
      相关资源
      最近更新 更多