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.
source to share
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" );
}
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>;
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.
source to share
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 .
source to share
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;
};
source to share
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 = "";
}
source to share