【问题标题】:How can I conditionally switch between two constructors with same signature?如何有条件地在具有相同签名的两个构造函数之间切换?
【发布时间】:2016-03-04 22:29:42
【问题描述】:

给定一个带有模板参数typename Tclass Tuple 的类,如果Tuple 具有std::vector 类成员函数reservepush_back,我想提供一个特殊的构造函数。如果Tuple 没有这样的成员函数,那么我想提供一个特殊的构造函数,如果Tuple 可以从可转换为T 的类型的可变参数构造,即

template<typename T, class Tuple>
class vector
{
    template<typename... Elements,
        typename = decltype(std::declval<Tuple>().push_back(T())),
        typename = decltype(std::declval<Tuple>().reserve(size_type()))>
    vector(Elements&&... elements)
    { /* ... */ }

    template<typename... Elements, typename = typename = decltype(Tuple{ static_cast<T>(std::declval<Elements>())... })>
    vector(Elements&&... elements)
    { /* ... */ }
};

问题 1: 显然,在上面的代码中,编译器并不知道我想尽可能采用第一个构造函数。无论如何,我怎样才能实现所需的行为?

问题2:假设第一个构造函数不存在,为什么下面的代码会导致编译器报错“cannot convert from initializer list to vector&lt;double, Tuple&lt;double&gt;&gt;”:

template<typename T>
class Tuple
{
public:
    Tuple() { }
    Tuple(std::initializer_list<T>) { }
};

int main()
{
    vector<double, Tuple<double>> x = { 1, 2, 3 };
    return 0;
}

【问题讨论】:

    标签: c++ templates c++14 variadic-templates typetraits


    【解决方案1】:

    假设我们有两种类型特征:

    template <class T, Tuple> struct satisfies_A;
    template <class T, Tuple> struct satisfies_B;
    

    我们希望提供一个构造函数来根据满意度调用其中一个。我们可以首先为每种情况添加直接构造函数,并带有一些额外的标记类型:

    template <class> struct tag{};
    
    template <typename T, class Tuple>
    class vector
    {
        struct A_tag { };
        struct B_tag { };
        struct na_tag { }; // if you want to support a fallback?
    
    public:
        template <class U=T, class UTuple=Tuple, class... Elements,
            class = std::enable_if_t<satsfies_A<U,UTuple>::value>>
        vector(tag<A_tag>, Elements&&... );
    
        template <class U=T, class UTuple=Tuple, class... Elements,
            class = std::enable_if_t<satsfies_B<U,UTuple>::value>>
        vector(tag<B_tag>, Elements&&... );
    };
    

    这些构造函数会根据两种不同的类型特征做任何你想让它们做的事情。现在,我们可以引入如下类型:

    using ctor_tag = std::conditional_t<
        satisfies_A<T, Tuple>::value,
        A_tag,
        std::conditional_t<
            satisfies_B<T, Tuple>::value,
            B_tag,
            na_tag>>; // or just void
    

    并酌情转发:

    template <class Element, class... Elements,
        class = std::enable_if_t<!is_template<tag, std::decay_t<Element>>::value>,
        class = std::enable_if_t<std::is_constructible<vector, tag<ctor_tag>, Element&&, Elements&&...>::value>>
    vector(Element&& element, Elements&&... elements)
    : vector(tag<ctor_tag>{}, std::forward<Element>(element), std::forward<Elements>(elements)...)
    { }
    

    类似的东西。

    【讨论】:

    • 两个条件(AB)都不满足时会发生什么。然后,ctor_tag 将是 no_tag。我是否需要为该标签提供第三个构造函数?
    • @0xbadf00d 这取决于不满足是否是逻辑错误。只要不满足前提条件之一(参数是已实现的类型),不实现 no_tag 案例就会为您提供编译器错误。这可能非常有用。
    • @RichardHodges 如果Tuple 合适,想法是有条件地启用这些构造函数。如果Tuple 不合适,则应禁用这些构造函数。不应产生编译器错误,因为我不想强制 Tuple 满足这两个条件之一。
    • 在这种情况下,是的,您还需要一个带有 no_tag 的构造函数
    • @RichardHodges 但我不想提供这样的构造函数。如果Tuple 满足两个条件之一,则vector::vector(Elements&amp;&amp;... elements) 形式的构造函数应该存在。否则,this 构造函数不应该存在。那么,我应该如何处理no_tag-constructor呢?
    【解决方案2】:

    当模板和构造函数参数既不是向量也不是元组时,我不清楚你想要什么行为。假设您只是希望这两个构造函数不符合条件,并且在尝试调用它们时不想要一些默认/回退行为(即,您将提供将被调用的其他构造函数),这里尝试解决您的特定问题:

    #include <type_traits>
    #include <utility>
    
    namespace detail {
    
    template<typename...>
    struct void_t_helper { using type = void; };
    
    template<typename... Ts>
    using void_t = typename void_t_helper<Ts...>::type;
    
    template<typename ElemT, typename T, typename SizeT = typename T::size_type>
    auto is_vectorlike(int, T& t)
     -> decltype(
            t.push_back(std::declval<ElemT>()),
            void(t.reserve(std::declval<SizeT>())),
            std::true_type()
        );
    
    template<typename, typename T>
    std::false_type is_vectorlike(long, T&);
    
    template<
        typename T, typename ElemT, typename... ArgTs,
        typename = void_t<decltype(static_cast<ElemT>(std::declval<ArgTs>()))...>
    >
    auto is_tuplelike(int)
     -> decltype(void(T{ std::declval<ArgTs>()... }), std::true_type());
    
    template<typename...>
    std::false_type is_tuplelike(long);
    
    }
    
    template<typename T, typename ElemT>
    using is_vectorlike = decltype(detail::is_vectorlike<ElemT>(0, std::declval<T&>()));
    
    template<typename T, typename ElemT, typename... ArgTs>
    using is_tuplelike = decltype(detail::is_tuplelike<T, ElemT, ArgTs...>(0));
    
    // ...
    
    template<typename T, typename Tuple>
    struct vector
    {
        vector() = default;
    
        template<typename Element, typename... Elements, typename = std::enable_if_t<
            is_vectorlike<Tuple, T>{} || is_tuplelike<Tuple, T, Element&&, Elements&&...>{}
        >>
        explicit vector(Element&& e, Elements&&... es)
          : vector(
                is_vectorlike<Tuple, T>{},
                is_tuplelike<Tuple, T, Element&&, Elements&&...>{},
                std::forward<Element>(e),
                std::forward<Elements>(es)...
            )
        { }
    
    private:
        template<typename _, typename... Elements>
        vector(std::true_type, _, Elements&&...)
        { /*vector-like*/ }
    
        template<typename... Elements>
        vector(std::false_type, std::true_type, Elements&&...)
        { /*tuple-like*/ }
    };
    

    注意在我看来,对于类似矢量的情况,您似乎也希望确保所有 Elements 都可以转换为 T,但是由于您没有这么说,所以这不会。 ;-]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-20
      • 2012-08-07
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多