2015-04-19 11 views
5

Z std::is_constructible można kwestionować pewne danego rodzaju na obecność pewnego konstruktora:C++ sprawdzić czy konstruktor zawiera parametr danego typu

struct A {}; 
struct B 
{ 
    explicit B(int, A, double) {} 
}; 

int main() 
{ 
    std::cout<<std::is_constructible<B,int,A,double>::value<<std::endl; //prints true 
} 

Załóżmy nie wiadomo typ B. Czy istnieje również sposób sprawdzenia, czy istnieje konstruktor w B, który zawiera typ A, niezależnie od innych parametrów? (-OR już wystarczające, który zawiera typ A w pozycji N-ty?)


Biorąc pod uwagę nie-explicit konstruktora I zdobione obejścia przy użyciu takiego typu, który może być przekształcony do niczego pośrednio :

struct convert_to_anything 
{ 
    template<typename T> 
    operator T() const 
    { 
     return T{};  
    } 
}; 

int main() 
{ 
    std::cout<<std::is_constructible<B, convert_to_anything, A, convert_to_anything>::value<<std::endl; 
} 

(Faktycznie, i nieoczekiwany dla mnie, znalazłem empirycznie, że wydaje się działać jak dobrze gdy explicit dodaje do konstruktora B ... a ja myślałem, że to zapobiec konwersji?)

Mimo tego obejścia musiałbym przetestować wszystkie możliwe liczby parametrów. Powiedzieć na A na pierwszej pozycji:

std::is_constructible<B, A>::value 
|| std::is_constructible<B, A, convert_to_anything>::value 
|| std::is_constructible<B, A, convert_to_anything, convert_to_anything>::value 
//... and so on up to a chosen maximum size. 

To wydaje się nieco niezadowalająca. Czy masz jakieś lepsze obejścia?

+0

„* Z std :: is_constructible można zakwestionować pewne danego typu na obecność pewnej konstruktor *” Faktycznie, może mieć pewne operator konwersji uginając Int i B bierze zamiast int. Nie możemy wiedzieć. – Columbo

+0

A nawet, "A" można skonstruować z 'int'. W jaki sposób możemy ustalić, że jest to 'int', a nie' char' lub 'long'? – Barry

+0

Takie testowanie odbywa się za pomocą SFINAE i działa tylko wtedy, gdy wiesz, czego dokładnie szukasz. Niemożliwe jest znalezienie konstruktora z dowolnymi właściwościami, ponieważ istnieje wiele takich konstruktorów, a system typu C++ nie ma reprezentacji dla typów "tego czy tamtego". –

Odpowiedz

4

Nie, w zasadzie nie ma innego sposobu, aby to osiągnąć. Jak sugerujesz, można użyć metaprogramowania w czasie kompilacji, aby ręcznie rozwinąć permutacje. Wierzę, że ogólne wdrożenie poniżej jest tak dobre, jak tylko możliwe. Zobacz szablon aliasu has_constructor_taking i jego użycie na dole kodu.

Poniższy kod wykorzystuje technikę template_worm opisuję jako here, która jest bardziej rozbudowaną implementacją Twojego convert_to_anything. Kod działa na najnowszych wersjach zarówno Clang, jak i GCC.

#include <utility> 
#include <type_traits> 
#include <tuple> 

namespace detail { 

    //template_worm CANNOT be used in evaluated contexts 
    struct template_worm { 

     template<typename T> 
     operator T&() const; 

     template<typename T> 
     operator T &&() const; 

     template_worm() = default; 

     template<typename... T> 
     template_worm(T&&...); 

     template_worm operator+() const; 
     template_worm operator-() const; 
     template_worm operator*() const; 
     template_worm operator&() const; 
     template_worm operator!() const; 
     template_worm operator~() const; 
     template_worm operator()(...) const; 
    }; 

#define TEMPLATE_WORM_BINARY_OPERATOR(...)         \ 
                      \ 
    template<typename T>             \ 
    constexpr inline auto             \ 
    __VA_ARGS__ (template_worm, T&&) -> template_worm {     \ 
     return template_worm{};           \ 
    }                  \ 
                      \ 
    template<typename T>             \ 
    constexpr inline auto             \ 
    __VA_ARGS__ (T&&, template_worm) -> template_worm {     \ 
     return template_worm{};           \ 
    }                  \ 
                      \ 
    constexpr inline auto             \ 
    __VA_ARGS__ (template_worm, template_worm) -> template_worm {   \ 
     return template_worm{};           \ 
    }                  \ 
    /**/ 

    TEMPLATE_WORM_BINARY_OPERATOR(operator+) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator-) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator/) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator*) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator==) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator!=) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator&&) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator||) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator|) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator&) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator%) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator,) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator<<) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator>>) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator<) 
    TEMPLATE_WORM_BINARY_OPERATOR(operator>) 

    template<typename T> 
    struct success : std::true_type {}; 

    template<typename T, typename... Args> 
    struct try_construct { 
     static constexpr bool value = std::is_constructible<T, Args...>::value; 
    }; 

    template<typename T> 
    struct try_construct<T, void> { 

     template<typename U> 
     static auto test(int) -> 
      success<decltype(U())>; 

     template<typename> 
     static std::false_type test(...); 

     static constexpr const bool value = decltype(test<T>(0))::value; 
    }; 

    template<typename T, typename ArgTuple, typename MappedSeq> 
    struct try_construct_helper; 

    template<typename T, typename ArgTuple, std::size_t... I> 
    struct try_construct_helper<T, ArgTuple, std::index_sequence<I...>> { 
     using type = try_construct<T, typename std::tuple_element<I, ArgTuple>::type...>; 
    }; 

    struct sentinel {}; 

    template<typename Target> 
    using arg_map = std::tuple<Target, template_worm const &>; 

    constexpr const std::size_t MappedTargetIndex = 0; 
    constexpr const std::size_t MappedWormIndex = 1; 

    template<std::size_t> 
    using worm_index = std::integral_constant<std::size_t, MappedWormIndex>; 

    template<typename SeqLeft, typename SeqRight> 
    struct map_indices; 

    template<std::size_t... Left, std::size_t... Right> 
    struct map_indices<std::index_sequence<Left...>, std::index_sequence<Right...>> { 

     using type = std::index_sequence< 
      worm_index<Left>::value..., 
      MappedTargetIndex, 
      worm_index<Right>::value... 
     >; 
    }; 

    template<std::size_t... Right> 
    struct map_indices<sentinel, std::index_sequence<Right...>> { 
     using type = std::index_sequence<0, worm_index<Right>::value...>; 
    }; 

    template<std::size_t... Left> 
    struct map_indices<std::index_sequence<Left...>, sentinel> { 
     using type = std::index_sequence<worm_index<Left>::value..., 0>; 
    }; 

    template<> 
    struct map_indices<sentinel, sentinel> { 
     using type = std::index_sequence<0>; 
    }; 

    template<std::size_t IncrementBy, typename Seq> 
    struct increment_seq; 

    template<std::size_t IncrementBy, std::size_t... I> 
    struct increment_seq<IncrementBy, std::index_sequence<I...>> { 
     using type = std::index_sequence<(I + IncrementBy)...>; 
    }; 

    // Checks the U constructor by passing TargetArg in every argument slot recursively 
    template<typename U, typename TargetArg, std::size_t TargetIndex, std::size_t Max, typename SeqOrSentinel> 
    struct try_constructors; 

    template<typename U, typename TargetArg, std::size_t TargetIndex, std::size_t Max> 
    struct try_constructors<U, TargetArg, TargetIndex, Max, sentinel> { 
     static constexpr const bool value = false; 
    }; 

    template<typename U, typename TargetArg, std::size_t TargetIndex, std::size_t Max, std::size_t... I> 
    struct try_constructors<U, TargetArg, TargetIndex, Max, std::index_sequence<I...>> { 

     using next = typename std::conditional< 
      sizeof...(I)+1 <= Max, 
      std::make_index_sequence<sizeof...(I)+1>, 
      sentinel 
     >::type; 

     using args = arg_map<TargetArg>; 

     using left_seq = typename std::conditional< 
      TargetIndex == 0, 
      sentinel, 
      std::make_index_sequence<TargetIndex> 
     >::type; 

     using right_seq_detail = typename increment_seq< 
      TargetIndex, 
      std::make_index_sequence<sizeof...(I)-TargetIndex> 
     >::type; 

     using right_seq = typename std::conditional< 
      TargetIndex == (sizeof...(I)), 
      sentinel, 
      right_seq_detail 
     >::type; 

     using mapped_seq = typename map_indices<left_seq, right_seq>::type; 

     static constexpr const bool value = std::disjunction< 
      typename try_construct_helper<U, args, mapped_seq>::type, 
      try_constructors<U, TargetArg, TargetIndex, Max, next> 
     >::value; 
    }; 

    // unrolls the constructor attempts using the argument counts in the SearchSeq range 
    template<typename T, typename TargetArg, typename SearchSeq> 
    struct try_constructors_outer; 

    template<typename T, typename TargetArg, std::size_t... TargetIndices> 
    struct try_constructors_outer<T, TargetArg, std::index_sequence<TargetIndices...>> { 

     static constexpr const bool value = std::disjunction< 
      try_constructors< 
       T, 
       TargetArg, 
       TargetIndices, 
       sizeof...(TargetIndices), 
       std::make_index_sequence<TargetIndices> 
      >... 
     >::value; 
    }; 

    template<typename T, std::size_t... TargetIndices> 
    struct try_constructors_outer<T, void, std::index_sequence<TargetIndices...>> { 
     static constexpr const bool value = try_construct<T, void>::value; 
    }; 
} 

// Here you go. 
template<typename TargetArg, typename T, std::size_t SearchLimit = 4> 
using has_constructor_taking = std::integral_constant<bool, 
    detail::try_constructors_outer< 
     T, 
     TargetArg, 
     std::make_index_sequence<SearchLimit> 
    >::value 
>; 

struct A {}; 

struct B { 
    B(int, A, double) {} 
}; 

struct C { 
    C() = delete; 
    C(C const &) = delete; 
}; 

static_assert(has_constructor_taking<A, B>::value, ""); 
static_assert(has_constructor_taking<int, B>::value, ""); 
static_assert(has_constructor_taking<double, B>::value, ""); 
static_assert(!has_constructor_taking<C, B>::value, ""); 
static_assert(!has_constructor_taking<const char*, B>::value, ""); 

static_assert(has_constructor_taking<void, A>::value, ""); 
static_assert(has_constructor_taking<A const &, A>::value, ""); 

static_assert(!has_constructor_taking<void, C>::value, ""); 
static_assert(!has_constructor_taking<C const &, C>::value, ""); 

int main() {}