2016-03-06 3 views
0

Пожалуйста, обратите внимание на следующий фрагмент кода:определения структуры, которая получена из true_type всякий раз, когда данный SFINAE-состоянии конструктор будет принято

template<typename T, class Tuple> 
class vector 
{ 
    using size_type = typename Tuple::size_type; 

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

Я хочу, чтобы определить вложенную-структуру supports_reserve_push_back, который является производным от std::true_type всякий раз, когда в конструктор выше был бы включен (и который получен от std::false_type в другом случае).

Как я могу это сделать?

+0

Если это единственный c'tor, это может привести к старому перегрузку с помощью метода varargs. – StoryTeller

+0

@StoryTeller Нет, есть больше конструкторов. В частности, есть другой конструктор с той же подписью. – 0xbadf00d

+0

Ну, очевидным решением было бы дублирование проверок, которые позволят c'tor. – StoryTeller

ответ

1
#include <type_traits> 
#include <utility> 

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

template <typename AlwaysVoid, template <typename...> class Operation, typename... Args> 
struct detect_impl : std::false_type {}; 

template <template <typename...> class Operation, typename... Args> 
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {}; 

template <template <typename...> class Operation, typename... Args> 
using detect = detect_impl<void_t<>, Operation, Args...>; 

template <typename T, typename Sz> 
using has_reserve = decltype(std::declval<T>().reserve(std::declval<Sz>())); 

template <typename T, typename U> 
using has_push_back = decltype(std::declval<T>().push_back(std::declval<U>())); 

template <typename Tuple, typename T, typename size_type> 
constexpr bool supports_reserve_push_back = detect<has_reserve, Tuple, size_type>{} && detect<has_push_back, Tuple, T>{}; 

Тест:

template <typename T, class Tuple> 
class vector 
{ 
public: 
    using size_type = typename Tuple::size_type; 

    template <typename... Elements, typename U = Tuple, 
    std::enable_if_t<supports_reserve_push_back<U&, T, size_type>, int> = 0> 
    vector(Elements&&... elements) 
    { 
    } 

    template <typename... Elements, typename U = Tuple, 
    std::enable_if_t<!supports_reserve_push_back<U&, T, size_type>, int> = 0> 
    vector(Elements&&... elements) 
    { 
    } 
}; 

DEMO

+0

В чем причина использования 'std :: declval ' вместо 'std :: declval ' in 'has_push_back'? И почему вы используете 'std :: declval ()' вместо 'U()'? Предположим, что класс 'T' имеет метод' push_back' для некоторого типа аргумента 'U'. Предположим, что 'U = foo' для некоторого класса' foo'. Будет ли 'has_push_back' оцениваться как true, даже если мы заменим' foo' на 'foo &&'? И как я могу гарантировать, что метод 'push_back'' T' принимает значение rvalue? – 0xbadf00d

+2

@ 0xbadf00d, используя 'U()' явно помещает требование к классу по умолчанию конструктивным. Это необоснованно, поскольку многие полезные классы не могут быть сделаны так. Вот и вся причина: 'std :: declval'. – StoryTeller

+0

@StoryTeller И в чем причина использования 'std :: declval ' вместо 'std :: declval '? – 0xbadf00d

0

Я хотел бы попробовать следующий подход:

иметь ваш vector шаблон класса унаследованы от суперкласса, как это:

template<typename T, class Tuple> class vector 
    : public supports_reserve_push_back_impl< 
     vector_has_default_constructor<T, Tuple>::value() > { 

// ... 

} 

Теперь определит класс vector_has_default_constructor шаблона, который принимает те же параметры шаблона:

template<typename T, class Tuple> class vector_has_default_constructor { 

public: 
// ... 

}; 

В vector_has_default_constructor:

  • Определите метод constexpr bool value() с точно такой же сигнатурой, как конструктор вектора. Этот метод constexpr возвращает true.

  • Определите перегрузку constexpr bool value() с подписями ..., которые должны иметь более низкий приоритет при разрешении перегрузки. Этот constexpr возвращает false.

Теперь эта ситуация сводится к определению двух тривиальных специализаций, supports_reserve_push_back_impl<true> и supports_reserve_push_back_impl<false>.

supports_reserve_push_back_impl<true> содержит ваше значение supports_reserve_push_back и наследуется вашим vector.

supports_reserve_push_back_impl<false> пуст.

2

Я изменил код, чтобы сделать его build. И реализовал признак, который вы запросили, насколько мне известно.

#include <iostream> 
#include <type_traits> 
#include <vector> 
#include <map> 

namespace example { 

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

template<typename T, class Tuple> 
struct vector { 
    using size_type = typename Tuple::size_type; 
    using tuple_type = Tuple; 
    using elem_type = T; 

    template<typename... Elements> 
    vector(Elements&&... elements) 
    { /* ... */ } 
}; 

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

template <class Vec> 
struct supports_reserve_push_back<Vec, void_t< 
    decltype(std::declval<typename Vec::tuple_type>().reserve(typename Vec::size_type())), 
    decltype(std::declval<typename Vec::tuple_type>().push_back(typename Vec::elem_type())) > 
> 
    : std::true_type {}; 

} 

int main() { 
    std::cout 
    << example::supports_reserve_push_back<example::vector<int, std::vector<int>>>::value 
    << '\n' 
    << example::supports_reserve_push_back<example::vector<int, std::map<int, int>>>::value; 
    return 0; 
} 

Несколько вещи, чтобы отметить:

  1. , как вы написали c'tor первоначально вызвал тяжелую ошибку при создании экземпляра класса в отрицательном случае. Вот почему я удалил chcck из c'tor.

  2. Я бы предложил сначала определить характерные черты и использовать их для включения ваших c'tors.

1
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,std::void_t<Z<Ts...>,Ts...>: 
    std::true_type{}; 
} 
template<template<class...>class Z,class... Ts> 
using can_apply=details::can_apply<Z,void,Ts...>; 

Оберните ваши decltypes в шаблон usings и сделать некоторые && и сделали.

Существует также экспериментальный метод, аналогичный описанному выше.

template<class T, class U> 
using push_back_r = decltype(std::declval<T>().push_back(std::declval<U>())); 
template<class T> 
using reserve_r = decltype(std::declval<T>().reserve(1)); 

template<class T, class U> 
constexpr can_apply<push_back_r,T,U> has_push_back={}; 
template<class T> 
constexpr can_apply<reserve_r,T> has_reserve={}; 

template<bool b>using bool_t=std::integral_constant<bool,b>; 

template<class T,class U> 
constexpr bool_t<has_push_back<T,U>&&has_reserve<T>> 
efficiently_fillable_with = {}; 

Тогда efficiently_fillable_with<T,U> истинно тип тогда и только тогда вы можете зарезервировать место с T, а затем столкнуть нас в него. Категория г/л значения T и U сохраняется: если вы хотите знать о заполнении нераскрытого cinstant именующего выражения из T с RValue U S:

efficiently_fillable_with<T&,U> 

Если вы хотите, чтобы заполнить U const& вместо rvalues, пройти U const&.

+0

Я предполагаю, что использование будет 'template using has_push_back = decltype (std :: declval () .push_back (std :: declval ())); 'и' template constexpr bool tuple_has_reserve_v = type_traits :: can_apply {}; ', правильно? Как проверить, если метод 'push_back' принимает значение rvalue (вместо lvalue или const)? – 0xbadf00d

+0

@ 0xbadf00d Удалите '&' s из ваших приложений, передайте '' '' '' '' '' '' '' '' '', чтобы описать значение параметра r/l. – Yakk

+0

Просьба уточнить. Что мне нужно изменить в 'has_push_back'? – 0xbadf00d