我正在玩模板,并试图实现以下帮助。
first_constructible<Types..., Args...>::type
将返回可从Types
构造的第一类Args...
。第一个问题显然是在struct
中有两个参数包,所以我改用了
first_constructible<std::tuple<Types...>, Args...>::type
我已经通过将元组类型拆分为第一个和休息来实现它,使用std::is_constructible
进行检查并在必要时进行递归。
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;
};
但由于某种原因它不起作用。即
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
我不知道我的错误在哪里。 std::is_constructible<std::string>::value
和std::is_constructible<std::string, std::string::size_type, std::string::value_type>::value
属实。
答案 0 :(得分:5)
首先,一些元编程玩具:
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...>>
{};
现在我们解决您的问题:
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...>>;
测试代码:
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,但仅适用于_t
别名。将std::foo_t<blah>
替换为typename std::foo<blah>::type
。
我所做的是找到每个可构造类型,然后抓住第一个。过滤器是一个简单的概念,我已经躺在那里,它比写“先通过测试”更容易,因为过滤器后面的第一个无条件在逻辑上是相同的(如果有点贵)。
您可以将上面的filter
修改为“短路”并在测试通过时返回而不是用尾部合并:
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...>>
{};
并将first_constructible
模板替换为:
template<class Tuple, class...Args>
using first_constructible = search_t<is_constructible_test<Args...>::template result, Tuple>;
我可能会使用像你那样与元组交互而不是专门化的实用函数,并且会有优势。
我看到你的一个问题是get<>
会返回引用,而不是值。 std::tuple_element_t
可能是一个更好的计划。
答案 1 :(得分:4)
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> {};
要实现C ++ 17 std::disjunction
,请参阅cppreference。
答案 2 :(得分:2)
我不知道究竟什么在你的解决方案中不起作用(关于using first_type
的事情,但无论如何),但你的解决方案过于复杂。
使用部分特化,您可以丢弃pop_front_tuple
并定义first_constructible
,如下所示。
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;
};
答案 3 :(得分:1)
还有一个 - 该方法避免了类型递归并改为使用constexpr函数递归(如果是c ++ 14,则此处甚至不需要递归):
#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 = "";
}