【问题标题】:finding type, for which is_constructible holds查找类型,is_constructible 持有该类型
【发布时间】:2017-05-10 20:08:48
【问题描述】:

我正在使用模板并试图实现以下帮助器。

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

这将返回Types 的第一种类型,它可以从Args... 构造。第一个问题显然是struct 中有两个参数包,所以我将用法改为

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

我通过将元组类型拆分为 first 和 rest 来实现它,使用 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&lt;std::string&gt;::valuestd::is_constructible&lt;std::string, std::string::size_type, std::string::value_type&gt;::value 是真的。

Coliru link

【问题讨论】:

    标签: c++ c++11 templates template-meta-programming


    【解决方案1】:

    首先,一些元编程玩具:

    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" );
    }
    

    live example.

    C++14,但仅适用于 _t 别名。将std::foo_t&lt;blah&gt; 替换为typename std::foo&lt;blah&gt;::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>;
    

    live example 2.

    我可能可以像你一样使用与元组交互而不是专门化的实用程序函数,并且会有优势。


    我看到你的一个问题是get&lt;&gt; 返回一个引用,而不是一个值。 std::tuple_element_t 可能是一个更好的计划。

    【讨论】:

    • "in some cases" 它在每一种情况中返回一个引用。
    • @T.C.观点;我忘记了它在传递右值元组和值元素时返回了一个右值引用。 (当我编写类似的函数时,我倾向于在那里返回一个值,因为我希望引用生命周期延长工作)。
    【解决方案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> {};
    

    有关 C++17 std::disjunction 的实现,请参阅 cppreference

    【讨论】:

      【解决方案3】:

      我不知道究竟是什么在您的解决方案中不起作用(无论如何,关于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;
       };
      

      【讨论】:

      • 没错。正如Yakk 提到的。 std::declval 返回引用,所以first_type 被推导出为std::string&amp;&amp;
      【解决方案4】:

      还有一个——该方法避免了类型递归,而是使用 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 = ""; 
      }
      

      [live demo]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多