如何使static_assert与SFINAE一起玩得很好

时间:2015-03-24 04:01:23

标签: c++ c++14 template-meta-programming sfinae static-assert

更新

我发布了rebind的工作草案作为问题的答案。虽然我没有太多运气找到一种保持static_assert不会破坏元功能的通用方法。


基本上我想检查是否可以从其他类型T<U, Args...>构造模板化类型T<V, Args...>。两个类型中TArgs...相同的位置。问题是,T<>可能有一个static_assert,完全打破了我的元功能。

以下是我正在尝试做的粗略总结。

template<typename T>
struct fake_alloc {
    using value_type = T;
};

template<typename T, typename Alloc = fake_alloc<T>>
struct fake_cont {
    using value_type = T;
    // comment the line below out, and it compiles, how can I get it to compile without commenting this out???
    static_assert(std::is_same<value_type, typename Alloc::value_type>::value, "must be the same type");
};

template<typename T, typename U, typename = void>
struct sample_rebind {
    using type = T;
};

template<template<typename...> class Container, typename T, typename U, typename... OtherArgs>
struct sample_rebind<
    Container<T, OtherArgs...>,
    U,
    std::enable_if_t<
        std::is_constructible<
            Container<T, OtherArgs...>,
            Container<U, OtherArgs...>
        >::value
    >
>
{
    using type = Container<U, OtherArgs...>;
};

static_assert(
    std::is_same<
        fake_cont<int, fake_alloc<int>>,
        typename sample_rebind<fake_cont<int>, double>::type
    >::value,
    "This should pass!"
);

正如您所看到的那样,期望的行为是最终的static_assert应该通过,但不幸的是,由于static_assert中的fake_cont被触发,它甚至无法达到这一点std::is_constructible<>尝试调用fake_cont的构造函数。

在实际代码fake_cont中是libc ++的std::vector,所以我无法修改它的胆量或std::is_constructible的胆量。

对于解决这一特定问题的任何建议表示赞赏,特别感谢SFINAE围绕static_assert的任何建议。

编辑:is_same的第一部分应该是fake_cont<int, fake_alloc<int>>

编辑2:如果您在static_assert中注释掉fake_cont,则会编译(俚语3.5)。这就是我想要的。所以我只需要一些方法来避免static_assert中的fake_cont

2 个答案:

答案 0 :(得分:1)

namespace details {
  template<class T,class=void>
  struct extra_test_t: std::true_type {};
}

然后我们在:

中折叠额外的测试
template<class...>struct types{using type=types;};

template<template<typename...> class Container, typename T, typename U, typename... OtherArgs>
struct sample_rebind<
  Container<T, OtherArgs...>,
  U,
  std::enable_if_t<
    details::extra_test_t< types< Container<T, OtherArgs...>, U > >::value
    && std::is_constructible<
      Container<T, OtherArgs...>,
      Container<U, OtherArgs...>
    >::value
  >
> {
  using type = Container<U, OtherArgs...>;
};

我们写了额外的测试:

namespace details {
  template<class T, class Alloc, class U>
  struct extra_test_t<
    types<std::vector<T,Alloc>, U>,
    typename std::enable_if<
      std::is_same<value_type, typename Alloc::value_type>::value
    >::type
  > : std::true_type {};
  template<class T, class Alloc, class U>
  struct extra_test_t<
    types<std::vector<T,Alloc>, U>,
    typename std::enable_if<
      !std::is_same<value_type, typename Alloc::value_type>::value
    >::type
  > : std::false_type {};
}

基本上,这可以让我们在测试中注入“补丁”以匹配static_assert

如果我们有is_std_container<T>get_allocator<T>,我们可以写:

namespace details {
  template<template<class...>class Z,class T, class...Other, class U>
  struct extra_test_t<
    types<Z<T,Other...>>, U>,
    typename std::enable_if<
       is_std_container<Z<T,Other...>>>::value
       && std::is_same<
         value_type,
         typename get_allocator<Z<T,Other...>>::value_type
       >::value
    >::type
  > : std::true_type {};
  template<class T, class Alloc, class U>
  struct extra_test_t<
    types<std::vector<T,Alloc>, U>,
    typename std::enable_if<
       is_std_container<Z<T,Other...>>>::value
       && !std::is_same<
         value_type,
         typename get_allocator<Z<T,Other...>>::value_type
       >::value
    >::type
  > : std::false_type {};
}

或者我们可以说任何allocator_type的东西都可能无法反弹。

解决此问题的容器更容易的方法是提取分配器类型(::allocator_type),并将容器参数列表中的所有allocator类型实例替换为T的重新绑定不知何故U。这仍然很棘手,因为std::map<int, int>具有std::allocator< std::pair<const int, int> >类型的分配器,并且以通用方式区分键int和值int是不可能的。 / p>

答案 1 :(得分:1)

我设法得到了一份非常可靠的重新开始的第一稿。它适用于所有STL容器(除了不常见的模板参数组合),容器适配器和std::integer_sequence。它也可能适用于更多的东西。但它肯定不会为一切工作而努力。

主要的麻烦是让类似地图的类型像Yakk预测的那样起作用,但是一点点的特性帮助了它。

关于代码......

<强> void_t

template<class...>
using void_t = void;

Walter E. Brown的这个小技巧使得实现类型特征变得更加容易。

类型特征

template<class T, class = void>
struct is_map_like : std::false_type {};

template<template<class...> class C, class First, class Second, class... Others>
struct is_map_like<C<First, Second, Others...>,
                   std::enable_if_t<std::is_same<typename C<First, Second, Others...>::value_type::first_type,
                                                 std::add_const_t<First>>{} &&
                                    std::is_same<typename C<First, Second, Others...>::value_type::second_type,
                                                 Second>{}>>
    : std::true_type {};

template<class T, class U, class = void>
struct has_mem_rebind : std::false_type {};

template<class T, class U>
struct has_mem_rebind<T, U, void_t<typename T::template rebind<U>>> : std::true_type {};

template<class T>
struct is_template_instantiation : std::false_type {};

template<template<class...> class C, class... Others>
struct is_template_instantiation<C<Others...>> : std::true_type {};
  1. is_map_like使用以下事实:STL中的类似地图的类型都value_type定义为带有std::pair ed第一个模板参数的{n} const类似地图的类型,是first_type中的pair。类似地图的类型的第二个模板参数与pair&#39; s second_type完全匹配。 rebind必须更仔细地处理类似地图的类型。
  2. has_mem_rebind使用rebind技巧在T上检测到成员void_t元功能的存在。如果一个类有rebind,那么我们将首先遵循类实现。
  3. is_template_instantiation检测类型T是否为模板实例化。这更适合调试。
  4. 帮助程序类型列表

    template<class... Types>
    struct pack
    {
        template<class T, class U>
        using replace = pack<
            std::conditional_t<
                std::is_same<Types, T>{},
                U,
                Types
            >...
        >;
        template<class T, class U>
        using replace_or_rebind = pack<
            std::conditional_t<
                std::is_same<Types, T>{},
                U,
                typename rebind<Types, U>::type
            >...
        >;
        template<class Not, class T, class U>
        using replace_or_rebind_if_not = pack<
            std::conditional_t<
                std::is_same<Types, Not>{},
                Types,
                std::conditional_t<
                    std::is_same<Types, T>{},
                    U,
                    typename rebind<Types, U>::type
                >
            >...
        >;
    
        template<class T>
        using push_front = pack<T, Types...>;
    };
    

    这处理一些简单的列表,如类型的操作

    1. replace以非递归方式替换所有T U次。
    2. replace_or_rebind将所有T替换为U,对于所有不匹配的匹配,会调用rebind
    3. replace_or_rebind_if_notreplace_or_rebind相同,但跳过符合Not的任何元素
    4. push_front只需将元素推送到type-list
    5. 的前面

      致电会员重新绑定

      // has member rebind implemented as alias
      template<class T, class U, class = void>
      struct do_mem_rebind
      {
          using type = typename T::template rebind<U>;
      };
      
      // has member rebind implemented as rebind::other
      template<class T, class U>
      struct do_mem_rebind<T, U, void_t<typename T::template rebind<U>::other>>
      {
          using type = typename T::template rebind<U>::other;
      };
      

      根据标准,有两种不同的有效方法来实现成员rebind。对于allocators rebind<T>::other rebind<T>。对于pointers,它只是do_mem_rebindrebind<T>::other的此实现与rebind<T>一起存在(如果存在),否则它将回归到更简单的template<template<class...> class C, class Pack> struct unpack; template<template<class...> class C, class... Args> struct unpack<C, pack<Args...>> { using type = C<Args...>; }; template<template<class...> class C, class Pack> using unpack_t = typename unpack<C, Pack>::type;

      <强>启封

      pack

      这需要C,提取它包含的类型,并将它们放入其他模板template<class T, class U, bool = is_map_like<T>{}, bool = std::is_lvalue_reference<T>{}, bool = std::is_rvalue_reference<T>{}, bool = has_mem_rebind<T, U>{}> struct rebind_impl { static_assert(!is_template_instantiation<T>{}, "Sorry. Rebind is not completely implemented."); using type = T; }; // map-like container template<class U, template<class...> class C, class First, class Second, class... Others> class rebind_impl<C<First, Second, Others...>, U, true, false, false, false> { using container_type = C<First, Second, Others...>; using value_type = typename container_type::value_type; using old_alloc_type = typename container_type::allocator_type; using other_replaced = typename pack<Others...>::template replace_or_rebind_if_not<old_alloc_type, First, typename U::first_type>; using new_alloc_type = typename std::allocator_traits<old_alloc_type>::template rebind_alloc<std::pair<std::add_const_t<typename U::first_type>, typename U::second_type>>; using replaced = typename other_replaced::template replace<old_alloc_type, new_alloc_type>; using tail = typename replaced::template push_front<typename U::second_type>; public: using type = unpack_t<C, typename tail::template push_front<typename U::first_type>>; }; // has member rebind template<class T, class U> struct rebind_impl<T, U, false, false, false, true> { using type = typename do_mem_rebind<T, U>::type; }; // has nothing, try rebind anyway template<template<class...> class C, class T, class U, class... Others> class rebind_impl<C<T, Others...>, U, false, false, false, false> { using tail = typename pack<Others...>::template replace_or_rebind<T, U>; public: using type = unpack_t<C, typename tail::template push_front<U>>; }; // has nothing, try rebind anyway, including casting NonType template parameters template<class T, template<class, T...> class C, class U, T FirstNonType, T... Others> struct rebind_impl<C<T, FirstNonType, Others...>, U, false, false, false, false> { using type = C<U, U(FirstNonType), U(Others)...>; }; // array takes a non-type parameter parameters template<class T, class U, std::size_t Size> struct rebind_impl<std::array<T, Size>, U, false, false, false, false> { using type = std::array<U, Size>; }; // pointer template<class T, class U> struct rebind_impl<T*, U, false, false, false, false> { using type = typename std::pointer_traits<T*>::template rebind<U>; }; // c-array template<class T, std::size_t Size, class U> struct rebind_impl<T[Size], U, false, false, false, false> { using type = U[Size]; }; // c-array2 template<class T, class U> struct rebind_impl<T[], U, false, false, false, false> { using type = U[]; }; // lvalue ref template<class T, class U> struct rebind_impl<T, U, false, true, false, false> { using type = std::add_lvalue_reference_t<std::remove_reference_t<U>>; }; // rvalue ref template<class T, class U> struct rebind_impl<T, U, false, false, true, false> { using type = std::add_rvalue_reference_t<std::remove_reference_t<U>>; };

      重新绑定实施

      好东西。

      rebind
      1. rebind<Types, double>...的失败案例是简单地保持类型不变。这样就可以调用Type,而无需担心Typesrebind中的每个static_assert是否都有rebind。如果收到模板实例,那里就有rebind。如果它被击中,您可能需要另一个rebind<std::map<int, int>, std::pair<double, std::string>>
      2. 的专业化
      3. 类似地图的replace_or_rebind_if_not期望像if_not一样被调用。所以分配器被反弹的类型与容器正在反弹的类型并不完全匹配。它会对除键和值类型之外的所有类型执行allocator_type,其中rebindconst。由于分配器类型与键/值对std::allocator_traits不同,因此需要修改该对的第一个元素的std::allocator_traits。它使用T来重新绑定分配器,因为所有分配器都必须通过rebind重新绑定。
      4. 如果T有成员rebind,请使用该作品。
      5. 如果replace_or_rebind没有成员CC模板T的所有参数都匹配U的第一个模板参数。< / LI>
      6. 如果std::integer_sequence有一个类型参数,并且有一堆类型与该参数匹配的非类型模板参数。尝试将所有这些非类型参数重新转换为std::array。这是使std::pointer_traits有效的情况。
      7. rebind需要一个特殊情况,因为它需要一个非类型模板参数给出它的大小,并且该模板参数应该保持不变。
      8. 这种情况允许重新绑定指向其他指针类型的指针。它使用rebind&#39; s T[5]来完成此任务。
      9. rebind处理大小的c-array ex:T[]
      10. rebind处理没有ex T
      11. 大小的c数组
      12. std::remove_reference_t<U> lvalue-ref rebind类型为保证左值 - 参考T
      13. std::remove_reference_t<U> s rvalue-ref template<class T, class U> struct rebind : details::rebind_impl<T, U> {}; template<class T, class U> using rebind_t = typename rebind<T, U>::type; 类型为保证的rvalue-ref为static_assert
      14. 派生(暴露)类

        static_assert

        返回SFINAE和template<class T> static_assert(CACHE_LINE_SIZE == 64, "") struct my_struct { ... };

        经过大量的谷歌搜索后,似乎不是{{1}}周围的SFINAE的通用方式,就像libc ++的STL容器中的那样。它真的让我希望这种语言有更多SFINAE友好的东西,但比概念更具特色。

        像:

        {{1}}