finding type, for which is_constructible holds

女生的网名这么多〃 提交于 2019-12-01 07:24:34

问题


I was playing around with templates and was trying to implement following helper.

first_constructible<Types..., Args...>::type

which would return first type of Types which is constructible from Args.... First problem obviously is having two parameter packs in struct, so I changed usage to

first_constructible<std::tuple<Types...>, Args...>::type

I've implemented it by splitting tuple types as first and rest, checked using std::is_constructible and recursed if neccessary.

template<typename T>
struct pop_front_tuple
{
    template<typename U, typename... Us>
    static std::tuple<Us...> impl(std::tuple<U, Us...>);

    using type = decltype(impl(std::declval<T>())); // std::tuple with removed first type
};

template<typename Tuple, typename... Args>
struct first_constructible
{
    using first_type = decltype(std::get<0>(std::declval<Tuple>()));

    using type = typename std::conditional
    <
        std::is_constructible<first_type, Args...>::value,
        first_type,
        typename first_constructible<typename pop_front_tuple<Tuple>::type, Args...>::type
    >::type;
};

// end of recursion
template<typename... Args>
struct first_constructible<std::tuple<>, Args...>
{
    using type = void;
};

but it for some reason does not work. I.e

first_constructible<std::tuple<std::string, int>, std::string>::type a = ""; // works, a is std::string
first_constructible<std::tuple<std::string, int>>::type a = ""; // fails, error: variable or field 'a' declared void
first_constructible<std::tuple<std::string, int>, std::string::size_type, std::string::value_type> // fails, same error

I don't know where my mistake is. std::is_constructible<std::string>::value and std::is_constructible<std::string, std::string::size_type, std::string::value_type>::value are true.

Coliru link


回答1:


First, some metaprogramming toys:

template<class Tag>
using type_t = typename Tag::type;
template<class T> struct tag_t{using type=T; constexpr tag_t(){}};
template<class T> constexpr tag_t<T> tag{};

template<class...Tuples>
using cat_tuples = decltype(std::tuple_cat( std::declval<Tuples>()... ));

template<template<class...>class Z, class Tuple, class=void>
struct filter;
template<template<class...>class Z, class Tuple>
using filter_t = type_t<filter<Z,Tuple>>;

template<template<class...>class Z>
struct filter<Z, std::tuple<>,void>:tag_t<std::tuple<>>{};
template<template<class...>class Z, class T0, class...Ts>
struct filter<Z, std::tuple<T0, Ts...>, std::enable_if_t<Z<T0>::value>>:
    tag_t<
        cat_tuples<
            std::tuple<T0>,
            filter_t<Z, std::tuple<Ts...>>
        >
    >
{};
template<template<class...>class Z, class T0, class...Ts>
struct filter<Z, std::tuple<T0, Ts...>, std::enable_if_t<!Z<T0>::value>>:
    filter<Z, std::tuple<Ts...>>
{};

Now we solve your problem:

template<class...Args>
struct is_constructible_test {
    template<class T>
    using result=std::is_constructible<T,Args...>;
};

template<class Tuple, class...Args>
using all_constructible_t = filter_t<is_constructible_test<Args...>::template result, Tuple>;

template<class Tuple, class...Args>
using first_constructible = std::tuple_element_t<0, all_constructible_t<Tuple,Args...>>;

Test code:

struct bob {
    bob( int, int, int ) {}
};
template<std::size_t>
struct alice {
    alice(int) {}
};

int main() {
    using is_alice = first_constructible<std::tuple<std::string, bob, alice<1>, alice<2>, int>, int>;
    static_assert( std::is_same<is_alice, alice<1>>::value, "works" );
}

live example.

C++14, but just for _t aliases. replace std::foo_t<blah> with typename std::foo<blah>::type.

What I did was find every constructible type, then grabbed the first one. Filter is a simple concept I had lying around, and it was easier than writing "get first that passes test", as filter followed by get first unconditional is logically the same (if a bit more expensive).

You could modify filter above to "short out" and return instead of concatinating with tail when the test passes:

template<template<class...>class Z, class Tuple, class=void>
struct search;
template<template<class...>class Z, class Tuple>
using search_t = type_t<search<Z,Tuple>>;

template<template<class...>class Z>
struct search<Z, std::tuple<>,void>{};
template<template<class...>class Z, class T0, class...Ts>
struct search<Z, std::tuple<T0, Ts...>, std::enable_if_t<Z<T0>::value>>:
    tag_t<T0>
{};
template<template<class...>class Z, class T0, class...Ts>
struct search<Z, std::tuple<T0, Ts...>, std::enable_if_t<!Z<T0>::value>>:
    search<Z, std::tuple<Ts...>>
{};

and replace the first_constructible template with:

template<class Tuple, class...Args>
using first_constructible = search_t<is_constructible_test<Args...>::template result, Tuple>;

live example 2.

I could probably use utility functions like you did that interacts with Tuples rather than specializing, and there would be advantages.


One issue I see with yours is that get<> returns a reference, not a value. std::tuple_element_t might be a better plan.




回答2:


template<class T, class...Args>
struct is_constructible_x : std::is_constructible<T, Args...> {
    using type = T;
};

struct not_found {
    static constexpr bool value = true;
    using type = void;
};

template<class, class...> struct first_constructible;

template<class...Ts, class... Args>
struct first_constructible<std::tuple<Ts...>, Args...> 
    : std::disjunction<is_constructible_x<Ts, Args...>..., not_found> {};

For implementation of C++17 std::disjunction, see cppreference.




回答3:


I don't know what, exactly, doesn't work in you solution (something regarding the using first_type, anyway) but your solution is overcomplicated.

Using partial specialization, you can throw away pop_front_tuple and define first_constructible as follows.

template <typename...>
struct first_constructible;

template <typename... Args>
struct first_constructible<std::tuple<>, Args...>
 { using type = void; };

template <typename First, typename ... Rest, typename... Args>
struct first_constructible<std::tuple<First, Rest...>, Args...>
 {
   using type = typename std::conditional<
      std::is_constructible<First, Args...>::value,
      First,
      typename first_constructible<std::tuple<Rest...>, Args...>::type
         >::type;
 };



回答4:


One more - the approach avoids type recursion and uses constexpr function recursion instead (if it was c++14, recursion wouldn't be even necessary here):

#include <type_traits>
#include <tuple>
#include <string>

template <std::size_t N>
constexpr std::size_t first_one(bool const (&c)[N], std::size_t I) {
    return (I == N)?N:(c[I]?I:first_one(c, I+1));
}

template <class Tuple, class... Args>
struct first_constructible;

template <class... Ts, class... Args>
struct first_constructible<std::tuple<Ts...>, Args...> {
    static constexpr bool constructible[] = { std::is_constructible<Ts, Args...>::value... };
    using type = typename std::tuple_element<first_one(constructible, 0), std::tuple<Ts..., void>>::type;
};

int main() {
    first_constructible<std::tuple<std::string, int>, std::string>::type a1 = ""; 
    first_constructible<std::tuple<std::string, int>>::type a2 = "";
    first_constructible<std::tuple<std::string, int>, std::string::size_type, std::string::value_type>::type a3 = ""; 
}

[live demo]



来源:https://stackoverflow.com/questions/43901862/finding-type-for-which-is-constructible-holds

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!