如何有条件地在具有相同签名的两个构造函数之间切换

How can I conditionally switch between two constructors with same signature?

本文关键字:两个 构造函数 之间 有条件      更新时间:2023-10-16

给定一个带有模板参数的类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:假设第一个构造函数不存在,为什么以下代码会导致编译器错误"无法从初始值设定项列表转换为 vector<double, Tuple<double>>":

template<typename T>
class Tuple
{
public:
    Tuple() { }
    Tuple(std::initializer_list<T>) { }
};
int main()
{
    vector<double, Tuple<double>> x = { 1, 2, 3 };
    return 0;
}

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

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)...)
{ }

类似的东西。

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

#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,但由于您没有这么说,因此没有。