如何将模板参数包参数限制为“链”序列?

SKN*_*KNB 18 c++ templates template-meta-programming c++-concepts c++20

假设我有两个课程:

\n
template <typename X, typename Y>\nclass Functor {};\n\ntemplate <typename Start, typename End, typename ...Functors>\nclass Template {};\n
Run Code Online (Sandbox Code Playgroud)\n

Template有以下限制:

\n
    \n
  • 全部Functors必须是类型Functor

    \n
  • \n
  • 所有内容都Functor必须按链式顺序排列,这样

    \n
      \n
    • 一个 Functor必须Start作为其第一个参数
    • \n
    • 最后 一个Functor必须End作为第二个参数
    • \n
    • 每个Functor\ 的第一个参数是前面它的第二个参数Functor
    • \n
    \n

    例如Functor<A,B>, Functor<B, C>, Functor<C, D>, ...等等。

    \n
  • \n
\n

例子:

\n

从...开始:char

\n

结尾为:long

\n

Template<char, long, Functor<char, A>, Functor<A, B>, Functor<B, C>, Functor<C, long>> t;

\n
                1         2         3         4\n           \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4\nargument: char       A         B         C        long\nFunctor #\n  = 1      Functor<char, A>,\n    2                Functor<A, B>,\n    3                           Functor<B, C>,\n    4                                    Functor<C, long>\n
Run Code Online (Sandbox Code Playgroud)\n

代码

\n
namespace ns\n{\n    template <typename X, typename Y = X>\n    class Functor\n    {\n    public:\n        using first  = X;\n        using second = Y;\n        Functor(X lVal) : x(lVal) {}\n    private:\n        X x;\n    };\n\n    template <typename Start, typename End, typename ...Functors>\n        requires(std::is_convertible_v<Functors, Functor> && ...)    //error\n    class Template\n    {\n        // How does one use `std::is_convertible_v` on\n        // an un-specialized template class?\n    };\n\n    template <typename Start, typename End>\n    class Template<Start, End, Functor<Start, End>>\n    {};\n}\n
Run Code Online (Sandbox Code Playgroud)\n

问题:

\n
    \n
  1. 最好的方法是什么?\n
      \n
    • 这可以通过折叠表达式来完成吗?
    • \n
    • 还是概念?
    • \n
    \n
  2. \n
  3. 如何在非专用模板类上使用std::is_convertible(或任何其他元编程特征)?
  4. \n
\n

Jar*_*d42 14

通过(ab)使用运算符重载,您可能会这样做

// Used std::type_identity as wrapper
// Operator+, but no implementation needed
template <typename A, typename B, typename C>
std::type_identity<Functor<A, C>>
operator +(std::type_identity<Functor<A, B>>, std::type_identity<Functor<B, C>>);
Run Code Online (Sandbox Code Playgroud)

然后只需检查操作是否“有效”。

template <typename Start, typename End, typename... Functors>
requires(std::is_same_v<std::type_identity<Functor<Start, End>>,
                        decltype((std::type_identity<Functors>{} + ...))>)
class Template {
    //...
};
Run Code Online (Sandbox Code Playgroud)

演示


Ted*_*gmo 10

您可以首先添加类型特征来检查模板参数的类型Functor

template <typename X, typename Y>
class Functor {
   public:
    using first_type = X;
    using second_type = Y;
};

// type trait to check if a type is a Functor
template <class...>
struct is_Functor : std::false_type {};

template <class X, class Y>
struct is_Functor<Functor<X, Y>> : std::true_type {};

template <class T>
inline constexpr bool is_Functor_v = is_Functor<T>::value;
Run Code Online (Sandbox Code Playgroud)

然后要求它们实际上都是Functor使用requires带有折叠表达式的子句的类型:

// Require Functors
template <typename Start, typename End, typename... Functors>
    requires(is_Functor_v<Functors> && ...)
class Template {
Run Code Online (Sandbox Code Playgroud)

然后断言第一个Functor作为Start第一个参数,最后一个Functor作为End第二个参数:

    // helper type to get a Functor (type) at a specific index:
    template <std::size_t I>
    using funcat = typename std::tuple_element_t<I, std::tuple<Functors...>>;

    static_assert(std::is_same_v<
                  Start, typename funcat<0>::first_type>);

    static_assert(std::is_same_v<
                  End, typename funcat<sizeof...(Functors) - 1>::second_type>);
Run Code Online (Sandbox Code Playgroud)

然后使用 lambda 和折叠表达式检查每个参数的第二个参数Functor与下一个参数的第一个参数的类型相同:Functor

    static_assert([]<std::size_t... I>(std::index_sequence<I...>) {
        return (... && std::is_same_v<typename funcat<I>::second_type,
                                      typename funcat<I + 1>::first_type>);
    }(std::make_index_sequence<sizeof...(Functors) - 1>()));
Run Code Online (Sandbox Code Playgroud)

如果您喜欢这样的声明,您也可以使用concept类型特征:is_Functor

template <class T>
concept functor = is_Functor_v<T>;

template <typename Start, typename End, functor... Functors>
class Template {
   //
};
Run Code Online (Sandbox Code Playgroud)

演示


至于第二个问题,“如何std::is_convertible在非专用模板类上使用(或任何其他元编程特征)?” ,您可以再次创建一个类型特征,或者concept检查 a 是否Functor可以由提供的类型实例化,而无需显式提供任何模板参数。

例子concept

template <class F, template <class...> class T>
concept deducible = requires(F&& f) {
    T(std::forward<F>(f));
};
Run Code Online (Sandbox Code Playgroud)

然后,类模板需要使用Functor每个实际模板参数将转换为的类型:

template <typename Start, typename End, deducible<Functor>... Functors>
class Template {
    template <std::size_t I>
    using funcat =
        std::tuple_element_t<I,
            std::tuple<decltype(Functor(std::declval<Functors>()))...>>;
    // converted to Functor     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^            

    static_assert(std::is_same_v<
                  Start, typename funcat<0>::first_type>,
                  "First Functor::first_type != Start");

    static_assert(std::is_same_v<
                  End, typename funcat<sizeof...(Functors) - 1>::second_type>,
                  "Last Functor::second_type != End");

    static_assert([]<std::size_t... I>(std::index_sequence<I...>) {
        return (... && std::is_same_v<typename funcat<I>::second_type,
                                      typename funcat<I + 1>::first_type>);
    }(std::make_index_sequence<sizeof...(Functors) - 1>()),
                  "Functor_n<..., T> != Functor_n+1<T, ...>");
};
Run Code Online (Sandbox Code Playgroud)

演示,其中 astd::pair<X, Y>可转换为 a Functor<X, Y>(奇怪的转换,但这只是一个示例)。


eca*_*mur 5

作为单个需求表达式:

    requires requires(Functors... f) {
        []<typename... X, typename... Y>(Functor<X, Y>&...)
            requires std::same_as<void(Start, Y...), void(X..., End)> {
        }(f...);
    }
Run Code Online (Sandbox Code Playgroud)

首先,我们推导出每个 的X和;如果其中任何一个不是 的实例,则此步骤将失败。(严格来说,它还允许从; 派生的类型,为了防止这种情况,您可以使用。)然后,我们检查类型链是否与链相同(注意:不是!);如果形成从到 的链,则这将精确成立。YFunctorFunctorsFunctorFunctor<X, Y>std::type_identityStart, Y...X..., End Start, X...<X, Y>...StartEnd

请注意,它也将保留 for <Start, End, Functor<Start, End>>,您已将其列为单独的情况,并且 for <Start, Start>,即如果StartEnd是相同类型,则它将允许链为空;如果您想禁止这样做,您可以添加sizeof...(Functors) != 0u一个额外的约束。

我使用函数类型作为类型列表,它很简洁,但确实存在类型衰减的潜在缺点;您同样可以使用eg std::tuple,这将允许放宽对eg的约束std::convertible_tostd::tuple<T...>可转换为std::tuple<U...>当且仅当每个T都可转换为其各自的U)。

如果传递了无效的链,gcc 将输出一个以类似以下内容结尾的错误:

note: the expression 'is_same_v<_Tp, _Up> [with _Tp = void(char, A, C, C, int); _Up = void(char, A, B, C, long int)]' evaluated to 'false'
Run Code Online (Sandbox Code Playgroud)

演示

编写约束的一种稍微详细但可能更清晰的方法是:

note: the expression 'is_same_v<_Tp, _Up> [with _Tp = void(char, A, C, C, int); _Up = void(char, A, B, C, long int)]' evaluated to 'false'
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用类型推导来形成类型列表C := {Start, Y...}D := {X..., End},然后对元素类型比较执行合取折叠,在无效链上给出如下错误:

note: the expression '(same_as<C, D> && ...) [with C = {char, A, C, C, int}; D = {char, A, B, C, long int}]' evaluated to 'false'
Run Code Online (Sandbox Code Playgroud)

演示

  • 同意,很有新意! (2认同)