The type of search for which is_constructible takes place

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

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

      

which will return the first type Types

that can be built from Args...

. The first problem obviously has two parameter packs in struct

, so I changed the usage to

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

      

I implemented it by separating the tuple types as first and remaining, checked with std::is_constructible

and recursively if necessary.

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 doesn't work for some reason. 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

+3


source to share


4 answers


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 will 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 only for aliases _t

. replace std::foo_t<blah>

with typename std::foo<blah>::type

.

What I did was find each construct type and then grab the first one. Filter is a simple concept I lay in and it was easier than writing "pass test first" since the filter followed by the first unconditional is logically the same (if slightly more expensive).



You can change the filter

above to "short out" and return instead of concatenating with the tail when passing the test:

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 template first_constructible

with:

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

      

live example 2 .

Perhaps I could use utility functions, as you did, that interact with Tuples rather than specialize, and there would be benefits.


One problem I see with yours is what is get<>

returning a reference, not a value. std::tuple_element_t

might be the best plan.

+5


source


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 a C ++ 17 implementation std::disjunction

see cppreference .

+4


source


I don't know what exactly does not work in your solution (something relative using first_type

, anyway), but your solution is more complicated.

Using partial specialization, you can drop 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;
 };

      

+2


source


Another - the approach avoids type recursion and instead uses constexpr function recursion (if it was C ++ 14, recursion was not even needed 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]

+1


source







All Articles