2016-03-04 1 views
1

Учитывая класс с параметрами шаблона typename T и class Tuple Я хочу предоставить специальный конструктор, если Tuple имеет std::vector-подобные функции-члены reserve и push_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; 
} 

ответ

2

Скажет у нас есть два типа признаков:

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

Что-то вроде этого.

+0

Что происходит, когда оба условия ('A' и' B') не выполняются. Тогда 'ctor_tag' будет' no_tag'. Должен ли я предоставить третий конструктор для этого тега? – 0xbadf00d

+0

@ 0xbadf00d зависит от того, является ли неудовлетворение логической ошибкой или нет. Невыполнение вопроса no_tag предоставило бы вам ошибку компилятора всякий раз, когда одно из предварительных условий (аргументы из реализованного типа) не выполняется. Это может быть очень полезно. –

+0

@RichardHodges Идея состоит в том, чтобы условно включить эти конструкторы, если «Tuple» подходит. Если 'Tuple' не подходит, то эти конструкторы должны быть отключены. Ошибка компилятора не должна генерироваться, поскольку я не хочу принуждать 'Tuple' удовлетворять одному из двух условий. – 0xbadf00d

1

Непонятно, какое поведение вы хотите, если аргументы шаблона и конструктора не являются ни векторными, ни кортежными. Предполагая, что вы просто хотите, чтобы эти два конструктора были неприемлемыми и не хотели какого-либо поведения по умолчанию/резервному при попытке вызвать их (т. Е. Вы предоставите другие конструкторы, которые будут вызваны вместо этого), вот попытка решить вашу конкретную проблему:

#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*/ } 
}; 

Nb мне кажется, что вы хотите, чтобы все Elements были конвертированы в T для векторного случая, но, так как вы этого не сказали, этого не происходит. ; -]